Draft: Replace QString cache IDs with a struct-based version

As it turns out, QString::number() is quite expensive, especially when
using it in a code path that is called a lot of times. So instead, use a
struct with a custom hash method as cache ID. This is significantly
faster since we do not need to do memory allocations or string
conversions.
This commit is contained in:
Marco Martin 2020-12-17 11:31:27 +00:00
parent b1b91fb3eb
commit f09b46bec6
11 changed files with 403 additions and 256 deletions

View File

@ -33,6 +33,7 @@ target_link_libraries(corebindingsplugin
KF5::Declarative
KF5::IconThemes
KF5::I18n
Qt5::Svg
KF5::Service #for kplugininfo.h
KF5::WindowSystem
KF5::Plasma

View File

@ -83,7 +83,7 @@ ecm_generate_export_header(KF5Plasma
GROUP_BASE_NAME KF
VERSION ${KF5_VERSION}
DEPRECATED_BASE_VERSION 0
DEPRECATION_VERSIONS 5.6 5.19 5.28 5.30 5.36 5.46 5.67 5.77
DEPRECATION_VERSIONS 5.6 5.19 5.28 5.30 5.36 5.46 5.67 5.77 5.78
)
# TODO: add support for EXCLUDE_DEPRECATED_BEFORE_AND_AT to all Plasma libs
# needs fixing of undeprecated API being still implemented using own deprecated API

View File

@ -27,7 +27,7 @@
namespace Plasma
{
QHash<ThemePrivate *, QHash<QString, QWeakPointer<FrameData>> > FrameSvgPrivate::s_sharedFrames;
QHash<ThemePrivate *, QHash<uint, QWeakPointer<FrameData>> > FrameSvgPrivate::s_sharedFrames;
// Any attempt to generate a frame whose width or height is larger than this
// will be rejected
@ -332,7 +332,7 @@ QRegion FrameSvg::mask() const
return result;
}
QString id = d->cacheId(d->frame.data(), QString());
uint id = qHash(d->cacheId(d->frame.data(), QString()), SvgRectsCache::s_seed);
QRegion* obj = d->frame->cachedMasks.object(id);
@ -459,7 +459,7 @@ QPixmap FrameSvgPrivate::alphaMask()
QSharedPointer<FrameData> FrameSvgPrivate::lookupOrCreateMaskFrame(const QSharedPointer<FrameData> &frame, const QString &maskPrefix, const QString &maskRequestedPrefix)
{
const QString key = cacheId(frame.data(), maskPrefix);
const uint key = qHash(cacheId(frame.data(), maskPrefix));
QSharedPointer<FrameData> mask = s_sharedFrames[q->theme()->d].value(key);
// See if we can find a suitable candidate in the shared frames.
@ -488,17 +488,19 @@ void FrameSvgPrivate::generateBackground(const QSharedPointer<FrameData> &frame)
return;
}
const QString id = cacheId(frame.data(), frame->prefix);
const uint id = qHash(cacheId(frame.data(), frame->prefix));
bool frameCached = !frame->cachedBackground.isNull();
bool overlayCached = false;
//TODO KF6: Kill Overlays
const bool overlayAvailable = !frame->prefix.startsWith(QLatin1String("mask-")) && q->hasElement(frame->prefix % QLatin1String("overlay"));
QPixmap overlay;
if (q->isUsingRenderingCache()) {
frameCached = q->theme()->findInCache(id, frame->cachedBackground, frame->lastModified) && !frame->cachedBackground.isNull();
frameCached = q->theme()->findInCache(QString::number(id), frame->cachedBackground, frame->lastModified) && !frame->cachedBackground.isNull();
if (overlayAvailable) {
overlayCached = q->theme()->findInCache(QLatin1String("overlay_") % id, overlay, frame->lastModified) && !overlay.isNull();
const uint overlayId = qHash(cacheId(frame.data(), frame->prefix % QLatin1String("overlay")));
overlayCached = q->theme()->findInCache(QString::number(overlayId), overlay, frame->lastModified) && !overlay.isNull();
}
}
@ -622,10 +624,10 @@ QRect FrameSvgPrivate::contentGeometry(const QSharedPointer<FrameData> &frame, c
void FrameSvgPrivate::updateFrameData(uint lastModified, UpdateType updateType)
{
auto fd = frame;
QString newKey;
uint newKey = 0;
if (fd) {
const QString oldKey = fd->cacheId;
const uint oldKey = fd->cacheId;
const QString oldPath = fd->imagePath;
const FrameSvg::EnabledBorders oldBorders = fd->enabledBorders;
@ -635,7 +637,7 @@ void FrameSvgPrivate::updateFrameData(uint lastModified, UpdateType updateType)
fd->frameSize = pendingFrameSize;
fd->imagePath = q->imagePath();
newKey = cacheId(fd.data(), prefix);
newKey = qHash(cacheId(fd.data(), prefix));
//reset frame to old values
fd->enabledBorders = oldBorders;
@ -671,8 +673,8 @@ void FrameSvgPrivate::updateFrameData(uint lastModified, UpdateType updateType)
fd->imagePath = q->imagePath();
fd->lastModified = lastModified;
//was fd just created empty now?
if (newKey.isEmpty()) {
newKey = cacheId(fd.data(), prefix);
if (newKey == 0) {
newKey = qHash(cacheId(fd.data(), prefix));
}
// we know it isn't in s_sharedFrames due to the check above, so insert it now
@ -752,11 +754,10 @@ void FrameSvgPrivate::paintCorner(QPainter& p, const QSharedPointer<FrameData> &
}
}
QString FrameSvgPrivate::cacheId(FrameData *frame, const QString &prefixToSave) const
SvgPrivate::CacheId FrameSvgPrivate::cacheId(FrameData *frame, const QString &prefixToSave) const
{
const QSize size = frameSize(frame).toSize();
const QLatin1Char s('_');
return QString::number(frame->enabledBorders) % s % QString::number(size.width()) % s % QString::number(size.height()) % s % QString::number(q->scaleFactor()) % s % QString::number(q->devicePixelRatio()) % s % prefixToSave % s % frame->imagePath;
return SvgPrivate::CacheId{double(size.width()), double(size.height()), frame->imagePath, prefixToSave, q->status(), q->scaleFactor(), q->devicePixelRatio(), q->colorGroup(), frame->enabledBorders, q->Svg::d->lastModified};
}
void FrameSvgPrivate::cacheFrame(const QString &prefixToSave, const QPixmap &background, const QPixmap &overlay)
@ -770,15 +771,16 @@ void FrameSvgPrivate::cacheFrame(const QString &prefixToSave, const QPixmap &bac
return;
}
const QString id = cacheId(frame.data(), prefixToSave);
const uint id = qHash(cacheId(frame.data(), prefixToSave));
//qCDebug(LOG_PLASMA)<<"Saving to cache frame"<<id;
q->theme()->insertIntoCache(id, background, QString::number((qint64)q, 16) % prefixToSave);
q->theme()->insertIntoCache(QString::number(id), background, QString::number((qint64)q, 16) % prefixToSave);
if (!overlay.isNull()) {
//insert overlay
q->theme()->insertIntoCache(QLatin1String("overlay_") % id, overlay, QString::number((qint64)q, 16) % prefixToSave % QLatin1String("overlay"));
const uint overlayId = qHash(cacheId(frame.data(), frame->prefix % QLatin1String("overlay")));
q->theme()->insertIntoCache(QString::number(overlayId), overlay, QString::number((qint64)q, 16) % prefixToSave % QLatin1String("overlay"));
}
}

View File

@ -16,6 +16,8 @@
#include <Plasma/Theme>
#include "svg_p.h"
namespace Plasma
{
@ -73,12 +75,12 @@ public:
QString requestedPrefix;
FrameSvg::EnabledBorders enabledBorders;
QPixmap cachedBackground;
QCache<QString, QRegion> cachedMasks;
QCache<uint, QRegion> cachedMasks;
static const int MAX_CACHED_MASKS = 10;
uint lastModified = 0;
QSize frameSize;
QString cacheId;
uint cacheId;
//measures
int topHeight;
@ -145,7 +147,7 @@ public:
void generateBackground(const QSharedPointer<FrameData> &frame);
void generateFrameBackground(const QSharedPointer<FrameData> &);
QString cacheId(FrameData *frame, const QString &prefixToUse) const;
SvgPrivate::CacheId cacheId(FrameData *frame, const QString &prefixToUse) const;
void cacheFrame(const QString &prefixToSave, const QPixmap &background, const QPixmap &overlay);
void updateSizes(FrameData* frame) const;
void updateSizes(const QSharedPointer<FrameData> &frame) const { return updateSizes(frame.data()); }
@ -178,7 +180,7 @@ public:
//this can differ from frame->frameSize if we are in a transition
QSize pendingFrameSize;
static QHash<ThemePrivate *, QHash<QString, QWeakPointer<FrameData>> > s_sharedFrames;
static QHash<ThemePrivate *, QHash<uint, QWeakPointer<FrameData>> > s_sharedFrames;
bool cacheAll : 1;
bool repaintBlocked : 1;

View File

@ -48,11 +48,24 @@ private:
class SvgPrivate
{
public:
struct CacheId {
double width;
double height;
QString filePath;
QString elementName;
int status;
double devicePixelRatio;
double scaleFactor;
int colorGroup;
uint extraFlags; //Not used here, used for enabledborders in FrameSvg
uint lastModified;
};
SvgPrivate(Svg *svg);
~SvgPrivate();
//This function is meant for the rects cache
QString cacheId(const QString &elementId) const;
CacheId cacheId(const QString &elementId) const;
//This function is meant for the pixmap cache
QString cachePath(const QString &path, const QSize &size) const;
@ -68,7 +81,7 @@ public:
void eraseRenderer();
QRectF elementRect(const QString &elementId);
QRectF findAndCacheElementRect(const QString &elementId, const QString &cacheId);
QRectF findAndCacheElementRect(const QString &elementId);
void checkColorHints();
@ -88,8 +101,6 @@ public:
Svg *q;
QPointer<Theme> theme;
QHash<QString, QRectF> localRectCache;
QMultiHash<QString, QSize> elementsWithSizeHints;
SharedSvgRenderer::Ptr renderer;
QString themePath;
QString path;
@ -111,7 +122,56 @@ public:
bool themeFailed : 1;
};
class SvgRectsCache : public QObject {
Q_OBJECT
public:
SvgRectsCache(QObject *parent = nullptr);
static SvgRectsCache *instance();
void insert(SvgPrivate::CacheId cacheId, const QRectF &rect, unsigned int lastModified);
void insert(uint id, const QString &filePath, const QRectF &rect, unsigned int lastModified);
// Those 2 methods are the same, the second uses the integer id produced by hashed CacheId
bool findElementRect(SvgPrivate::CacheId cacheId, QRectF &rect);
bool findElementRect(uint id, const QString &filePath, QRectF &rect);
void loadImageFromCache(const QString &path, uint lastModified);
void dropImageFromCache(const QString &path);
void expireCache(const QString &path);
void setNaturalSize(const QString &path, qreal scaleFactor, const QSizeF &size);
QSizeF naturalSize(const QString &path, qreal scaleFactor);
QList<QSize> sizeHintsForId(const QString &path, const QString &id);
void insertSizeHintForId(const QString &path, const QString &id, const QSize &size);
QString iconThemePath();
void setIconThemePath(const QString &path);
QStringList cachedKeysForPath(const QString &path) const;
void updateLastModified(const QString &filePath, unsigned int lastModified);
static const uint s_seed;
private:
QTimer *m_configSyncTimer = nullptr;
QString m_iconThemePath;
KSharedConfigPtr m_svgElementsCache;
/*
* We are indexing in the hash cache ids by their "digested" uint out of qHash(CacheId)
* because we need to serialize it and unserialize it to a config file,
* which is more efficient to do that with the uint directly rather than a CacheId struct serialization
*/
QHash<uint, QRectF> m_localRectCache;
QHash<QString, QSet<unsigned int>> m_invalidElements;
QHash<QString, QList<QSize>> m_sizeHintsForId;
};
}
uint qHash(const Plasma::SvgPrivate::CacheId &id, uint seed = 0);
#endif

View File

@ -8,6 +8,7 @@
#include "theme_p.h"
#include "framesvg.h"
#include "framesvg_p.h"
#include "svg_p.h"
#include "debug_p.h"
#include <QGuiApplication>
@ -75,12 +76,6 @@ ThemePrivate::ThemePrivate(QObject *parent)
pixmapSaveTimer->setInterval(600);
QObject::connect(pixmapSaveTimer, &QTimer::timeout, this, &ThemePrivate::scheduledCacheUpdate);
rectSaveTimer = new QTimer(this);
rectSaveTimer->setSingleShot(true);
//2 minutes
rectSaveTimer->setInterval(2 * 60 * 1000);
QObject::connect(rectSaveTimer, &QTimer::timeout, this, &ThemePrivate::saveSvgElementsCache);
updateNotificationTimer = new QTimer(this);
updateNotificationTimer->setSingleShot(true);
updateNotificationTimer->setInterval(100);
@ -121,7 +116,6 @@ ThemePrivate::ThemePrivate(QObject *parent)
ThemePrivate::~ThemePrivate()
{
saveSvgElementsCache();
FrameSvgPrivate::s_sharedFrames.remove(this);
delete pixmapCache;
}
@ -239,43 +233,24 @@ bool ThemePrivate::useCache()
ThemeConfig config;
pixmapCache = new KImageCache(cacheFile, config.themeCacheKb() * 1024);
pixmapCache->setEvictionPolicy(KSharedDataCache::EvictLeastRecentlyUsed);
if (cachesTooOld) {
discardCache(PixmapCache | SvgElementsCache);
}
}
if (cacheTheme && !svgElementsCache) {
const QString svgElementsFileNameBase = QLatin1String("plasma-svgelements-") + themeName;
QString svgElementsFileName = svgElementsFileNameBase;
if (!themeVersion.isEmpty()) {
svgElementsFileName += QLatin1String("_v") + themeVersion;
}
// now we check for (and remove) old caches
QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation));
cacheDir.setNameFilters(QStringList({svgElementsFileNameBase + QLatin1Char('*')}));
const auto files = cacheDir.entryInfoList();
for (const QFileInfo &file : files) {
if (!file.absoluteFilePath().endsWith(svgElementsFileName)) {
QFile::remove(file.absoluteFilePath());
}
}
const QString svgElementsFile = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1Char('/') + svgElementsFileName;
svgElementsCache = KSharedConfig::openConfig(svgElementsFile, KConfig::SimpleConfig);
if (cacheTheme) {
QString currentIconThemePath;
const auto *iconTheme = KIconLoader::global()->theme();
if (iconTheme) {
currentIconThemePath = iconTheme->dir();
}
KConfigGroup globalGroup(svgElementsCache, QLatin1String("Global"));
const QString oldIconThemePath = globalGroup.readEntry("currentIconThemePath", QString());
const QString oldIconThemePath = SvgRectsCache::instance()->iconThemePath();
if (oldIconThemePath != currentIconThemePath) {
discardCache(PixmapCache | SvgElementsCache);
globalGroup.writeEntry("currentIconThemePath", currentIconThemePath);
svgElementsCache = KSharedConfig::openConfig(svgElementsFile, KConfig::SimpleConfig);
SvgRectsCache::instance()->setIconThemePath(currentIconThemePath);
}
}
@ -357,9 +332,6 @@ void ThemePrivate::discardCache(CacheTypes caches)
if (caches & SvgElementsCache) {
discoveries.clear();
invalidElements.clear();
svgElementsCache = nullptr;
}
}
@ -657,21 +629,6 @@ void ThemePrivate::settingsChanged(bool emitChanges)
setThemeName(cg.readEntry("name", ThemePrivate::defaultTheme), false, emitChanges);
}
void ThemePrivate::saveSvgElementsCache()
{
if (svgElementsCache) {
QHashIterator<QString, QSet<QString> > it(invalidElements);
while (it.hasNext()) {
it.next();
KConfigGroup imageGroup(svgElementsCache, it.key());
imageGroup.writeEntry("invalidElements", it.value().values()); //FIXME: add QSet support to KConfig
}
//Pretty drastic, but this is executed only very rarely
svgElementsCache->sync();
}
}
QColor ThemePrivate::color(Theme::ColorRole role, Theme::ColorGroup group) const
{
const KColorScheme *scheme = nullptr;

View File

@ -78,7 +78,6 @@ public Q_SLOTS:
void onAppExitCleanup();
void notifyOfChanged();
void settingsChanged(bool emitChanges);
void saveSvgElementsCache();
Q_SIGNALS:
void themeChanged();
@ -116,9 +115,7 @@ public:
int defaultWallpaperWidth;
int defaultWallpaperHeight;
KImageCache *pixmapCache;
KSharedConfigPtr svgElementsCache;
QString cachedDefaultStyleSheet;
QHash<QString, QSet<QString> > invalidElements;
QHash<QString, QPixmap> pixmapsToCache;
QHash<QString, QString> keysToCache;
QHash<QString, QString> idsToCache;
@ -126,7 +123,6 @@ public:
QHash<Theme::ColorGroup, QString> cachedSelectedSvgStyleSheets;
QHash<QString, QString> discoveries;
QTimer *pixmapSaveTimer;
QTimer *rectSaveTimer;
QTimer *updateNotificationTimer;
unsigned cacheSize;
CacheTypes cachesToDiscard;

View File

@ -34,9 +34,36 @@
#include "theme.h"
#include "debug_p.h"
uint qHash(const Plasma::SvgPrivate::CacheId &id, uint seed)
{
std::array<uint, 10> parts = {
::qHash(id.width),
::qHash(id.height),
::qHash(id.elementName),
::qHash(id.filePath),
::qHash(id.status),
::qHash(id.devicePixelRatio),
::qHash(id.scaleFactor),
::qHash(id.colorGroup),
::qHash(id.extraFlags),
::qHash(id.lastModified)
};
return qHashRange(parts.begin(), parts.end(), seed);
}
namespace Plasma
{
class SvgRectsCacheSingleton
{
public:
SvgRectsCache self;
};
Q_GLOBAL_STATIC(SvgRectsCacheSingleton, privateSvgRectsCacheSelf)
const uint SvgRectsCache::s_seed = 0x9e3779b9;
SharedSvgRenderer::SharedSvgRenderer(QObject *parent)
: QSvgRenderer(parent)
@ -123,9 +150,217 @@ bool SharedSvgRenderer::load(
return true;
}
#define QLSEP QLatin1Char('_')
#define CACHE_ID_WITH_SIZE(size, id, status, devicePixelRatio) QString::number(int(size.width())) % QLSEP % QString::number(int(size.height())) % QLSEP % id % QLSEP % QString::number(status) % QLSEP % QString::number(devicePixelRatio)
#define CACHE_ID_NATURAL_SIZE(id, status, devicePixelRatio) QLatin1String("Natural") % QLSEP % id % QLSEP % QString::number(status) % QLSEP % QString::number(devicePixelRatio)
SvgRectsCache::SvgRectsCache(QObject *parent)
: QObject(parent)
{
const QString svgElementsFile = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1Char('/') + QStringLiteral("plasma-svgelements");
m_svgElementsCache = KSharedConfig::openConfig(svgElementsFile, KConfig::SimpleConfig);
m_configSyncTimer = new QTimer(this);
m_configSyncTimer->setSingleShot(true);
m_configSyncTimer->setInterval(5000);
connect(m_configSyncTimer, &QTimer::timeout,
this, [this]() {
m_svgElementsCache->sync();
});
}
SvgRectsCache *SvgRectsCache::instance()
{
return &privateSvgRectsCacheSelf()->self;
}
void SvgRectsCache::insert(Plasma::SvgPrivate::CacheId cacheId, const QRectF &rect, unsigned int lastModified)
{
insert(qHash(cacheId, SvgRectsCache::s_seed), cacheId.filePath, rect, lastModified);
}
void SvgRectsCache::insert(uint id, const QString &filePath, const QRectF &rect, unsigned int lastModified)
{
if (m_localRectCache.contains(id)) {
return;
}
m_localRectCache.insert(id, rect);
KConfigGroup imageGroup(m_svgElementsCache, filePath);
imageGroup.writeEntry("LastModified", lastModified);
if (rect.isValid()) {
imageGroup.writeEntry(QString::number(id), rect);
} else {
m_invalidElements[filePath] << id;
imageGroup.writeEntry("Invalidelements", m_invalidElements[filePath].values());
}
m_configSyncTimer->start();
}
bool SvgRectsCache::findElementRect(Plasma::SvgPrivate::CacheId cacheId, QRectF &rect)
{
return findElementRect(qHash(cacheId, SvgRectsCache::s_seed), cacheId.filePath, rect);
}
bool SvgRectsCache::findElementRect(uint id, const QString &filePath, QRectF &rect)
{
auto it = m_localRectCache.find(id);
if (it == m_localRectCache.end()) {
if (m_invalidElements.contains(filePath) && m_invalidElements[filePath].contains(id)) {
rect = QRectF();
return true;
}
return false;
}
rect = *it;
return true;
}
void SvgRectsCache::loadImageFromCache(const QString &path, uint lastModified)
{
if (path.isEmpty()) {
return;
}
KConfigGroup imageGroup(m_svgElementsCache, path);
unsigned int savedTime = imageGroup.readEntry("LastModified", 0);
if (lastModified > savedTime) {
imageGroup.deleteGroup();
m_configSyncTimer->start();
return;
}
auto list = imageGroup.readEntry("Invalidelements", QList<unsigned int>());
m_invalidElements[path] = QSet<unsigned int>(list.begin(), list.end());
for (const auto &key : imageGroup.keyList()) {
bool ok = false;
if (ok) {
const QRectF rect = imageGroup.readEntry(key, QRectF());
m_localRectCache.insert(key.toUInt(), rect);
}
}
}
void SvgRectsCache::dropImageFromCache(const QString &path)
{
KConfigGroup imageGroup(m_svgElementsCache, path);
imageGroup.deleteGroup();
m_configSyncTimer->start();
}
QList<QSize> SvgRectsCache::sizeHintsForId(const QString &path, const QString &id)
{
const QString pathId = path % id;
if (!m_sizeHintsForId.contains(pathId)) {
KConfigGroup imageGroup(m_svgElementsCache, path);
const QStringList &encoded = imageGroup.readEntry(id, QStringList());
QList<QSize> sizes;
for (const auto &token : encoded) {
const auto &parts = token.split(QLatin1Char('x'));
if (parts.size() != 2) {
continue;
}
QSize size = QSize(parts[0].toDouble(), parts[1].toDouble());
if (!size.isEmpty()) {
sizes << size;
}
}
m_sizeHintsForId[pathId] = sizes;
return sizes;
}
return m_sizeHintsForId.value(pathId);
}
void SvgRectsCache::insertSizeHintForId(const QString &path, const QString &id, const QSize &size)
{
//TODO: need to make this more efficient
auto sizeListToString = [] (const QList<QSize> &list) {
QString ret;
for (const auto &s : list) {
ret += QString::number(s.width()) % QLatin1Char('x') % QString::number(s.height()) % QLatin1Char(',');
}
return ret;
};
m_sizeHintsForId[path % id].append(size);
KConfigGroup imageGroup(m_svgElementsCache, path);
imageGroup.writeEntry(id, sizeListToString(m_sizeHintsForId[path % id]));
m_configSyncTimer->start();
}
QString SvgRectsCache::iconThemePath()
{
if (!m_iconThemePath.isEmpty()) {
return m_iconThemePath;
}
KConfigGroup imageGroup(m_svgElementsCache, QStringLiteral("General"));
m_iconThemePath = imageGroup.readEntry(QStringLiteral("IconThemePath"), QString());
return m_iconThemePath;
}
void SvgRectsCache::setIconThemePath(const QString &path)
{
m_iconThemePath = path;
KConfigGroup imageGroup(m_svgElementsCache, QStringLiteral("General"));
imageGroup.writeEntry(QStringLiteral("IconThemePath"), path);
m_configSyncTimer->start();
}
void SvgRectsCache::expireCache(const QString &path)
{
KConfigGroup imageGroup(m_svgElementsCache, path);
unsigned int savedTime = imageGroup.readEntry("LastModified", QDateTime().toSecsSinceEpoch());
QFileInfo info(path);
if (info.exists()) {
unsigned int lastModified = info.lastModified().toSecsSinceEpoch();
if (lastModified <= savedTime) {
return;
}
}
imageGroup.deleteGroup();
}
void SvgRectsCache::setNaturalSize(const QString &path, qreal scaleFactor, const QSizeF &size)
{
KConfigGroup imageGroup(m_svgElementsCache, path);
// FIXME: needs something faster, perhaps even sprintf
imageGroup.writeEntry(QStringLiteral("NaturalSize_") % QString::number(scaleFactor), size);
m_configSyncTimer->start();
}
QSizeF SvgRectsCache::naturalSize(const QString &path, qreal scaleFactor)
{
KConfigGroup imageGroup(m_svgElementsCache, path);
// FIXME: needs something faster, perhaps even sprintf
return imageGroup.readEntry(QStringLiteral("NaturalSize_") % QString::number(scaleFactor), QSizeF());
}
QStringList SvgRectsCache::cachedKeysForPath(const QString &path) const
{
KConfigGroup imageGroup(m_svgElementsCache, path);
QStringList list = imageGroup.keyList();
QStringList filtered;
std::copy_if (list.begin(), list.end(), std::back_inserter(filtered), [](const QString element){bool ok; element.toLong(&ok); return ok;} );
return filtered;
}
void SvgRectsCache::updateLastModified(const QString &filePath, unsigned int lastModified)
{
KConfigGroup imageGroup(m_svgElementsCache, filePath);
imageGroup.writeEntry("LastModified", lastModified);
m_configSyncTimer->start();
}
SvgPrivate::SvgPrivate(Svg *svg)
: q(svg),
@ -153,19 +388,17 @@ SvgPrivate::~SvgPrivate()
}
//This function is meant for the rects cache
QString SvgPrivate::cacheId(const QString &elementId) const
SvgPrivate::CacheId SvgPrivate::cacheId(const QString &elementId) const
{
if (size.isValid() && size != naturalSize) {
return CACHE_ID_WITH_SIZE(size, elementId, status, devicePixelRatio);
} else {
return CACHE_ID_NATURAL_SIZE(elementId, status, devicePixelRatio);
}
auto idSize = size.isValid() && size != naturalSize ? size : QSizeF{-1.0, -1.0};
return CacheId{idSize.width(), idSize.height(), path, elementId, status, devicePixelRatio, scaleFactor, -1, 0, lastModified};
}
//This function is meant for the pixmap cache
QString SvgPrivate::cachePath(const QString &path, const QSize &size) const
QString SvgPrivate::cachePath(const QString &id, const QSize &size) const
{
return CACHE_ID_WITH_SIZE(size, path, status, devicePixelRatio) % QLSEP % QString::number(colorGroup);
auto cacheId = CacheId{double(size.width()), double(size.height()), path, id, status, devicePixelRatio, scaleFactor, colorGroup, 0, lastModified};
return QString::number(qHash(cacheId, SvgRectsCache::s_seed));
}
bool SvgPrivate::setImagePath(const QString &imagePath)
@ -207,8 +440,7 @@ bool SvgPrivate::setImagePath(const QString &imagePath)
themed = isThemed;
path.clear();
themePath.clear();
localRectCache.clear();
elementsWithSizeHints.clear();
bool oldFromCurrentTheme = fromCurrentTheme;
fromCurrentTheme = !inIconTheme && isThemed && actualTheme()->currentThemeHasImage(imagePath);
@ -234,6 +466,12 @@ bool SvgPrivate::setImagePath(const QString &imagePath)
#endif
}
QFileInfo info(path);
lastModified = info.lastModified().toSecsSinceEpoch();
SvgRectsCache::instance()->loadImageFromCache(path, lastModified);
// check if svg wants colorscheme applied
checkColorHints();
@ -242,20 +480,14 @@ bool SvgPrivate::setImagePath(const QString &imagePath)
if ((themed && !path.isEmpty() && QFileInfo::exists(path)) || QFileInfo::exists(actualPath)) {
QRectF rect;
if (cacheAndColorsTheme()->findInRectsCache(path, QStringLiteral("_Natural_%1").arg(scaleFactor), rect)) {
naturalSize = rect.size();
} else {
naturalSize = SvgRectsCache::instance()->naturalSize(path, scaleFactor);
if (naturalSize.isEmpty()) {
createRenderer();
naturalSize = renderer->defaultSize() * scaleFactor;
//qCDebug(LOG_PLASMA) << "natural size for" << path << "from renderer is" << naturalSize;
cacheAndColorsTheme()->insertIntoRectsCache(path, QStringLiteral("_Natural_%1").arg(scaleFactor), QRectF(QPointF(0, 0), naturalSize));
//qCDebug(LOG_PLASMA) << "natural size for" << path << "from cache is" << naturalSize;
SvgRectsCache::instance()->setNaturalSize(path, scaleFactor, naturalSize);
}
}
QFileInfo info(path);
lastModified = info.lastModified().toSecsSinceEpoch();
q->resize();
emit q->imagePathChanged();
@ -291,39 +523,15 @@ QPixmap SvgPrivate::findInCache(const QString &elementId, qreal ratio, const QSi
QSize size;
QString actualElementId;
if (elementsWithSizeHints.isEmpty()) {
// Fetch all size hinted element ids from the theme's rect cache
// and store them locally.
const QRegularExpression sizeHintedKeyExpr(QLatin1String("^") + CACHE_ID_NATURAL_SIZE(QStringLiteral("(\\d+)-(\\d+)-(.+)"), status, ratio) + QLatin1String("$"));
const auto lst = cacheAndColorsTheme()->listCachedRectKeys(path);
for (const QString &key : lst) {
const auto match = sizeHintedKeyExpr.match(key);
if (match.hasMatch()) {
QString baseElementId = match.captured(3);
QSize sizeHint(match.capturedRef(1).toInt(),
match.capturedRef(2).toInt());
if (sizeHint.isValid()) {
elementsWithSizeHints.insert(baseElementId, sizeHint);
}
}
}
if (elementsWithSizeHints.isEmpty()) {
// Make sure we won't query the theme unnecessarily.
elementsWithSizeHints.insert(QString(), QSize());
}
}
// Look at the size hinted elements and try to find the smallest one with an
// identical aspect ratio.
if (s.isValid() && !elementId.isEmpty()) {
const QList<QSize> elementSizeHints = elementsWithSizeHints.values(elementId);
const QList<QSize> elementSizeHints = SvgRectsCache::instance()->sizeHintsForId(path, elementId);
if (!elementSizeHints.isEmpty()) {
QSize bestFit(-1, -1);
QSizeF bestFit(-1, -1);
for (const QSize &hint : elementSizeHints) {
for (const auto &hint : elementSizeHints) {
if (hint.width() >= s.width() * ratio && hint.height() >= s.height() * ratio &&
(!bestFit.isValid() ||
@ -353,9 +561,7 @@ QPixmap SvgPrivate::findInCache(const QString &elementId, qreal ratio, const QSi
return QPixmap();
}
const QString id = cachePath(path, size) + actualElementId;
//qCDebug(LOG_PLASMA) << "id is " << id;
const QString id = cachePath(actualElementId, size);
QPixmap p;
if (cacheRendering && cacheAndColorsTheme()->findInCache(id, p, lastModified)) {
@ -364,11 +570,6 @@ QPixmap SvgPrivate::findInCache(const QString &elementId, qreal ratio, const QSi
return p;
}
//qCDebug(LOG_PLASMA) << "didn't find cached version of " << id << ", so re-rendering";
//qCDebug(LOG_PLASMA) << "size for " << actualElementId << " is " << s;
// we have to re-render this puppy
createRenderer();
QRectF finalRect = makeUniform(renderer->boundsOnElement(actualElementId), QRect(QPoint(0, 0), size));
@ -397,9 +598,11 @@ QPixmap SvgPrivate::findInCache(const QString &elementId, qreal ratio, const QSi
}
if (cacheRendering) {
cacheAndColorsTheme()->insertIntoCache(id, p, QString::number((qint64)q, 16) % QLSEP % actualElementId);
cacheAndColorsTheme()->insertIntoCache(id, p, QString::number((qint64)q, 16) % QLatin1Char('_') % actualElementId);
}
SvgRectsCache::instance()->updateLastModified(path, lastModified);
return p;
}
@ -409,7 +612,6 @@ void SvgPrivate::createRenderer()
return;
}
//qCDebug(LOG_PLASMA) << kBacktrace();
if (themed && path.isEmpty() && !themeFailed) {
Applet *applet = qobject_cast<Applet *>(q->parent());
//FIXME: this maybe could be more efficient if we knew if the package was empty, e.g. for
@ -433,17 +635,12 @@ void SvgPrivate::createRenderer()
}
}
//qCDebug(LOG_PLASMA) << "********************************";
//qCDebug(LOG_PLASMA) << "FAIL! **************************";
//qCDebug(LOG_PLASMA) << path << "**";
QString styleSheet = cacheAndColorsTheme()->d->svgStyleSheet(colorGroup, status);
styleCrc = qChecksum(styleSheet.toUtf8().constData(), styleSheet.size());
QHash<QString, SharedSvgRenderer::Ptr>::const_iterator it = s_renderers.constFind(styleCrc + path);
if (it != s_renderers.constEnd()) {
//qCDebug(LOG_PLASMA) << "gots us an existing one!";
renderer = it.value();
} else {
if (path.isEmpty()) {
@ -455,14 +652,19 @@ void SvgPrivate::createRenderer()
// Add interesting elements to the theme's rect cache.
QHashIterator<QString, QRectF> i(interestingElements);
QRegularExpression sizeHintedKeyExpr(QStringLiteral("^(\\d+)-(\\d+)-(.+)$"));
while (i.hasNext()) {
i.next();
const QString &elementId = i.key();
QString originalId = i.key();
const QRectF &elementRect = i.value();
const QString cacheId = CACHE_ID_NATURAL_SIZE(elementId, status, devicePixelRatio);
localRectCache.insert(cacheId, elementRect);
cacheAndColorsTheme()->insertIntoRectsCache(path, cacheId, elementRect);
originalId.replace(sizeHintedKeyExpr, QStringLiteral("\\3"));
SvgRectsCache::instance()->insertSizeHintForId(path, originalId, elementRect.size().toSize());
const CacheId cacheId({-1.0, -1.0, path, elementId, status, devicePixelRatio, scaleFactor, -1, 0, lastModified});
SvgRectsCache::instance()->insert(cacheId, elementRect, lastModified);
}
}
@ -484,16 +686,10 @@ void SvgPrivate::eraseRenderer()
#endif
// this and the cache reference it
s_renderers.erase(s_renderers.find(styleCrc + path));
if (theme) {
theme.data()->releaseRectsCache(path);
}
}
renderer = nullptr;
styleCrc = 0;
localRectCache.clear();
elementsWithSizeHints.clear();
}
QRectF SvgPrivate::elementRect(const QString &elementId)
@ -515,35 +711,23 @@ QRectF SvgPrivate::elementRect(const QString &elementId)
return QRectF();
}
const QString id = cacheId(elementId);
const auto it = localRectCache.constFind(id);
if (it != localRectCache.constEnd()) {
return *it;
}
QRectF rect;
bool found = cacheAndColorsTheme()->findInRectsCache(path, id, rect);
const CacheId cacheId = SvgPrivate::cacheId(elementId);
bool found = SvgRectsCache::instance()->findElementRect(cacheId, rect);
//This is a corner case where we are *sure* the element is not valid
if (found && rect == QRectF()) {
return rect;
} else if (found) {
localRectCache.insert(id, rect);
} else {
rect = findAndCacheElementRect(elementId, id);
if (!found) {
rect = findAndCacheElementRect(elementId);
}
return rect;
}
QRectF SvgPrivate::findAndCacheElementRect(const QString &elementId, const QString &id)
QRectF SvgPrivate::findAndCacheElementRect(const QString &elementId)
{
//we need to check the id before createRenderer(), otherwise it may generate a different id compared to the previous cacheId)( call
createRenderer();
const CacheId cacheId = SvgPrivate::cacheId(elementId);
const auto it = localRectCache.constFind(id);
if (it != localRectCache.constEnd()) {
return *it;
}
createRenderer();
//This code will usually never be run because createRenderer already caches all the boundingRect in the elements in the svg
QRectF elementRect = renderer->elementExists(elementId) ?
@ -560,9 +744,7 @@ QRectF SvgPrivate::findAndCacheElementRect(const QString &elementId, const QStri
elementRect = QRectF(elementRect.x() * dx, elementRect.y() * dy,
elementRect.width() * dx, elementRect.height() * dy);
cacheAndColorsTheme()->insertIntoRectsCache(path, id, elementRect);
localRectCache.insert(id, elementRect);
SvgRectsCache::instance()->insert(cacheId, elementRect, lastModified);
return elementRect;
}
@ -642,7 +824,6 @@ QRectF SvgPrivate::makeUniform(const QRectF &orig, const QRectF &dst)
res.setHeight(res.height() + offset);
}
//qCDebug(LOG_PLASMA)<<"Aligning Rects, origin:"<<orig<<"destination:"<<dst<<"result:"<<res;
return res;
}
@ -732,9 +913,8 @@ void Svg::setScaleFactor(qreal ratio)
//not resize() because we want to do it unconditionally
QRectF rect;
if (d->cacheAndColorsTheme()->findInRectsCache(d->path, QStringLiteral("_Natural_%1").arg(d->scaleFactor), rect)) {
d->naturalSize = rect.size();
} else {
d->naturalSize = SvgRectsCache::instance()->naturalSize(d->path, d->scaleFactor);
if (d->naturalSize.isEmpty()) {
d->createRenderer();
d->naturalSize = d->renderer->defaultSize() * d->scaleFactor;
}
@ -840,7 +1020,6 @@ void Svg::resize(const QSizeF &size)
}
d->size = size;
d->localRectCache.clear();
emit sizeChanged();
}
@ -852,7 +1031,6 @@ void Svg::resize()
}
d->size = d->naturalSize;
d->localRectCache.clear();
emit sizeChanged();
}
@ -883,14 +1061,14 @@ bool Svg::isValid() const
//try very hard to avoid creation of a parser
QRectF rect;
if (d->cacheAndColorsTheme()->findInRectsCache(d->path, QStringLiteral("_Natural_%1").arg(d->scaleFactor), rect)) {
QSizeF naturalSize = SvgRectsCache::instance()->naturalSize(d->path, d->scaleFactor);
if (!naturalSize.isEmpty()) {
return true;
}
if (d->path.isEmpty() || !QFileInfo::exists(d->path)) {
return false;
}
d->createRenderer();
return d->renderer->isValid();
}
@ -908,7 +1086,6 @@ bool Svg::containsMultipleImages() const
void Svg::setImagePath(const QString &svgFilePath)
{
if (d->setImagePath(svgFilePath)) {
//qCDebug(LOG_PLASMA) << "repaintNeeded";
emit repaintNeeded();
}
}

View File

@ -6,6 +6,7 @@
#include "theme.h"
#include "private/theme_p.h"
#include "private/svg_p.h"
#include <QFile>
#include <QFontDatabase>
@ -38,11 +39,11 @@ Theme::Theme(QObject *parent)
{
if (!ThemePrivate::globalTheme) {
ThemePrivate::globalTheme = new ThemePrivate;
ThemePrivate::globalTheme->settingsChanged(false);
}
ThemePrivate::globalTheme->ref.ref();
d = ThemePrivate::globalTheme;
d->settingsChanged(false);
if (QCoreApplication::instance()) {
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit,
d, &ThemePrivate::onAppExitCleanup);
@ -335,36 +336,13 @@ bool Theme::findInRectsCache(const QString &image, const QString &element, QRect
return false;
}
KConfigGroup imageGroup(d->svgElementsCache, image);
rect = imageGroup.readEntry(element % QLatin1String("Size"), QRectF());
if (rect.isValid()) {
return true;
}
//Name starting by _ means the element is empty and we're asked for the size of
//the whole image, so the whole image is never invalid
if (element.indexOf(QLatin1Char('_')) <= 0) {
bool ok = false;
uint id = element.toLong(&ok);
if (!ok) {
return false;
}
bool invalid = false;
QHash<QString, QSet<QString> >::iterator it = d->invalidElements.find(image);
if (it == d->invalidElements.end()) {
const QStringList elementList = imageGroup.readEntry("invalidElements", QStringList());
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
const QSet<QString> elements(elementList.begin(), elementList.end());
#else
const QSet<QString> elements = elementList.toSet();
#endif
d->invalidElements.insert(image, elements);
invalid = elements.contains(element);
} else {
invalid = it.value().contains(element);
}
return invalid;
return SvgRectsCache::instance()->findElementRect(id, image, rect);
}
QStringList Theme::listCachedRectKeys(const QString &image) const
@ -373,21 +351,7 @@ QStringList Theme::listCachedRectKeys(const QString &image) const
return QStringList();
}
KConfigGroup imageGroup(d->svgElementsCache, image);
QStringList keys = imageGroup.keyList();
QMutableListIterator<QString> i(keys);
while (i.hasNext()) {
QString key = i.next();
if (key.endsWith(QLatin1String("Size"))) {
// The actual cache id used from outside doesn't end on "Size".
key.resize(key.size() - 4);
i.setValue(key);
} else {
i.remove();
}
}
return keys;
return SvgRectsCache::instance()->cachedKeysForPath(image);
}
void Theme::insertIntoRectsCache(const QString &image, const QString &element, const QRectF &rect)
@ -396,46 +360,26 @@ void Theme::insertIntoRectsCache(const QString &image, const QString &element, c
return;
}
if (rect.isValid()) {
KConfigGroup imageGroup(d->svgElementsCache, image);
imageGroup.writeEntry(element % QLatin1String("Size"), rect);
} else {
QHash<QString, QSet<QString> >::iterator it = d->invalidElements.find(image);
if (it == d->invalidElements.end()) {
d->invalidElements[image].insert(element);
} else if (!it.value().contains(element)) {
if (it.value().count() > 1000) {
it.value().erase(it.value().begin());
}
it.value().insert(element);
}
bool ok = false;
uint id = element.toLong(&ok);
if (!ok) {
return;
}
QMetaObject::invokeMethod(d->rectSaveTimer, "start");
uint secs = QDateTime::currentSecsSinceEpoch();
SvgRectsCache::instance()->insert(id, image, rect, secs);
}
void Theme::invalidateRectsCache(const QString &image)
{
if (d->useCache()) {
KConfigGroup imageGroup(d->svgElementsCache, image);
imageGroup.deleteGroup();
}
d->invalidElements.remove(image);
SvgRectsCache::instance()->dropImageFromCache(image);
}
void Theme::releaseRectsCache(const QString &image)
{
QHash<QString, QSet<QString> >::iterator it = d->invalidElements.find(image);
if (it != d->invalidElements.end()) {
if (d->useCache()) {
KConfigGroup imageGroup(d->svgElementsCache, it.key());
imageGroup.writeEntry("invalidElements", it.value().values());
}
d->invalidElements.erase(it);
}
Q_UNUSED(image);
// No op: the internal svg cache always writes the invalid elements in the proper place
}
void Theme::setCacheLimit(int kbytes)

View File

@ -270,6 +270,7 @@ public:
**/
void setCacheLimit(int kbytes);
#if PLASMA_ENABLE_DEPRECATED_SINCE(5, 78)
/**
* Tries to load the rect of a sub element from a disk cache
*
@ -279,6 +280,7 @@ public:
* if not found or if we are sure it doesn't exist it will be QRect()
* @return true if the element was found in cache or if we are sure the element doesn't exist
**/
PLASMA_DEPRECATED_VERSION(5, 78, "Rects Cache public API is deprecated")
bool findInRectsCache(const QString &image, const QString &element, QRectF &rect) const;
/**
@ -290,6 +292,7 @@ public:
*
* @since 4.6
*/
PLASMA_DEPRECATED_VERSION(5, 78, "Rects Cache public API is deprecated")
QStringList listCachedRectKeys(const QString &image) const;
/**
@ -299,6 +302,7 @@ public:
* @param element sub element we want insert the rect
* @param rect element rectangle
**/
PLASMA_DEPRECATED_VERSION(5, 78, "Rects Cache public API is deprecated")
void insertIntoRectsCache(const QString &image, const QString &element, const QRectF &rect);
/**
@ -306,6 +310,7 @@ public:
*
* @param image the path to the image the cache is associated with
**/
PLASMA_DEPRECATED_VERSION(5, 78, "Rects Cache public API is deprecated")
void invalidateRectsCache(const QString &image);
/**
@ -315,7 +320,9 @@ public:
*
* @param image the path to the image the cache is associated with
*/
PLASMA_DEPRECATED_VERSION(5, 78, "Rects Cache public API is deprecated")
void releaseRectsCache(const QString &image);
#endif
#if PLASMA_ENABLE_DEPRECATED_SINCE(5, 67)
/**

View File

@ -46,6 +46,7 @@ target_link_libraries(KF5PlasmaQuick
KF5::Plasma
KF5::WindowSystem
PRIVATE
Qt5::Svg
KF5::KIOWidgets
KF5::I18n
KF5::IconThemes