File indexing completed on 2024-05-19 04:26:39

0001 /*
0002  *  SPDX-FileCopyrightText: 2010 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_update_scheduler.h"
0008 
0009 #include "klocalizedstring.h"
0010 #include "kis_image_config.h"
0011 #include "kis_merge_walker.h"
0012 #include "kis_full_refresh_walker.h"
0013 
0014 #include "kis_updater_context.h"
0015 #include "kis_simple_update_queue.h"
0016 #include "kis_strokes_queue.h"
0017 
0018 #include "kis_queues_progress_updater.h"
0019 #include "KisImageConfigNotifier.h"
0020 
0021 #include <QReadWriteLock>
0022 #include "kis_lazy_wait_condition.h"
0023 #include <mutex>
0024 
0025 //#define DEBUG_BALANCING
0026 
0027 #ifdef DEBUG_BALANCING
0028 #define DEBUG_BALANCING_METRICS(decidedFirst, excl)                     \
0029     dbgKrita << "Balance decision:" << decidedFirst                     \
0030     << "(" << excl << ")"                                               \
0031     << "updates:" << m_d->updatesQueue.sizeMetric()                    \
0032     << "strokes:" << m_d->strokesQueue.sizeMetric()
0033 #else
0034 #define DEBUG_BALANCING_METRICS(decidedFirst, excl)
0035 #endif
0036 
0037 
0038 struct Q_DECL_HIDDEN KisUpdateScheduler::Private {
0039     Private(KisUpdateScheduler *_q, KisProjectionUpdateListener *p)
0040         : q(_q)
0041         , updaterContext(KisImageConfig(true).maxNumberOfThreads(), q)
0042         , projectionUpdateListener(p)
0043     {}
0044 
0045     KisUpdateScheduler *q;
0046 
0047     KisSimpleUpdateQueue updatesQueue;
0048     KisStrokesQueue strokesQueue;
0049     KisUpdaterContext updaterContext;
0050     bool processingBlocked = false;
0051     qreal defaultBalancingRatio = 1.0; // desired strokes-queue-size / updates-queue-size
0052     KisProjectionUpdateListener *projectionUpdateListener;
0053     KisQueuesProgressUpdater *progressUpdater = 0;
0054 
0055     QAtomicInt updatesLockCounter;
0056     QReadWriteLock updatesStartLock;
0057     KisLazyWaitCondition updatesFinishedCondition;
0058 
0059     qreal balancingRatio() const {
0060         const qreal strokeRatioOverride = strokesQueue.balancingRatioOverride();
0061         return strokeRatioOverride > 0 ? strokeRatioOverride : defaultBalancingRatio;
0062     }
0063 };
0064 
0065 KisUpdateScheduler::KisUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener, QObject *parent)
0066     : QObject(parent),
0067       m_d(new Private(this, projectionUpdateListener))
0068 {
0069     updateSettings();
0070     connectSignals();
0071 }
0072 
0073 KisUpdateScheduler::KisUpdateScheduler()
0074     : m_d(new Private(this, 0))
0075 {
0076 }
0077 
0078 KisUpdateScheduler::~KisUpdateScheduler()
0079 {
0080     delete m_d->progressUpdater;
0081     delete m_d;
0082 }
0083 
0084 void KisUpdateScheduler::setThreadsLimit(int value)
0085 {
0086     KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->processingBlocked);
0087 
0088     /**
0089      * Thread limit can be changed without the full-featured barrier
0090      * lock, we can avoid waiting for all the jobs to complete. We
0091      * should just ensure there is no more jobs in the updater context.
0092      */
0093     immediateLockForReadOnly();
0094     m_d->updaterContext.lock();
0095     m_d->updaterContext.setThreadsLimit(value);
0096     m_d->updaterContext.unlock();
0097     unlock(false);
0098 }
0099 
0100 int KisUpdateScheduler::threadsLimit() const
0101 {
0102     std::lock_guard<KisUpdaterContext> l(m_d->updaterContext);
0103     return m_d->updaterContext.threadsLimit();
0104 }
0105 
0106 void KisUpdateScheduler::connectSignals()
0107 {
0108     connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()),
0109             SLOT(updateSettings()));
0110 }
0111 
0112 void KisUpdateScheduler::setProgressProxy(KoProgressProxy *progressProxy)
0113 {
0114     delete m_d->progressUpdater;
0115     m_d->progressUpdater = progressProxy ?
0116         new KisQueuesProgressUpdater(progressProxy, this) : 0;
0117 }
0118 
0119 void KisUpdateScheduler::progressUpdate()
0120 {
0121     if (!m_d->progressUpdater) return;
0122 
0123     if(!m_d->strokesQueue.hasOpenedStrokes()) {
0124         QString jobName = m_d->strokesQueue.currentStrokeName().toString();
0125         if(jobName.isEmpty()) {
0126             jobName = i18n("Updating...");
0127         }
0128 
0129         int sizeMetric = m_d->strokesQueue.sizeMetric();
0130         if (!sizeMetric) {
0131             sizeMetric = m_d->updatesQueue.sizeMetric();
0132         }
0133 
0134         m_d->progressUpdater->updateProgress(sizeMetric, jobName);
0135     }
0136     else {
0137         m_d->progressUpdater->hide();
0138     }
0139 }
0140 
0141 void KisUpdateScheduler::updateProjection(KisNodeSP node, const QVector<QRect> &rects, const QRect &cropRect)
0142 {
0143     m_d->updatesQueue.addUpdateJob(node, rects, cropRect, currentLevelOfDetail());
0144     processQueues();
0145 }
0146 
0147 void KisUpdateScheduler::updateProjection(KisNodeSP node, const QRect &rc, const QRect &cropRect)
0148 {
0149     m_d->updatesQueue.addUpdateJob(node, rc, cropRect, currentLevelOfDetail());
0150     processQueues();
0151 }
0152 
0153 void KisUpdateScheduler::updateProjectionNoFilthy(KisNodeSP node, const QVector<QRect>& rects, const QRect &cropRect)
0154 {
0155     m_d->updatesQueue.addUpdateNoFilthyJob(node, rects, cropRect, currentLevelOfDetail());
0156     processQueues();
0157 }
0158 
0159 void KisUpdateScheduler::updateProjectionNoFilthy(KisNodeSP node, const QRect& rc, const QRect &cropRect)
0160 {
0161     m_d->updatesQueue.addUpdateNoFilthyJob(node, rc, cropRect, currentLevelOfDetail());
0162     processQueues();
0163 }
0164 
0165 void KisUpdateScheduler::fullRefreshAsync(KisNodeSP root, const QVector<QRect>& rects, const QRect &cropRect)
0166 {
0167     m_d->updatesQueue.addFullRefreshJob(root, rects, cropRect, currentLevelOfDetail());
0168     processQueues();
0169 }
0170 
0171 void KisUpdateScheduler::fullRefreshAsyncNoFilthy(KisNodeSP root, const QVector<QRect> &rects, const QRect &cropRect)
0172 {
0173     m_d->updatesQueue.addFullRefreshNoFilthyJob(root, rects, cropRect, currentLevelOfDetail());
0174     processQueues();
0175 }
0176 
0177 void KisUpdateScheduler::fullRefresh(KisNodeSP root, const QRect& rc, const QRect &cropRect)
0178 {
0179     KisBaseRectsWalkerSP walker = new KisFullRefreshWalker(cropRect);
0180     walker->collectRects(root, rc);
0181 
0182     bool needLock = true;
0183 
0184     if(m_d->processingBlocked) {
0185         warnImage << "WARNING: Calling synchronous fullRefresh under a scheduler lock held";
0186         warnImage << "We will not assert for now, but please port caller's to strokes";
0187         warnImage << "to avoid this warning";
0188         needLock = false;
0189     }
0190 
0191     if(needLock) immediateLockForReadOnly();
0192     m_d->updaterContext.lock();
0193 
0194     Q_ASSERT(m_d->updaterContext.isJobAllowed(walker));
0195     m_d->updaterContext.addMergeJob(walker);
0196     m_d->updaterContext.unlock();
0197 
0198     m_d->updaterContext.waitForDone();
0199 
0200     if(needLock) unlock(true);
0201 }
0202 
0203 void KisUpdateScheduler::addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
0204 {
0205     m_d->updatesQueue.addSpontaneousJob(spontaneousJob);
0206     processQueues();
0207 }
0208 
0209 bool KisUpdateScheduler::hasUpdatesRunning() const
0210 {
0211     return !m_d->updatesQueue.isEmpty();
0212 }
0213 
0214 KisStrokeId KisUpdateScheduler::startStroke(KisStrokeStrategy *strokeStrategy)
0215 {
0216     KisStrokeId id  = m_d->strokesQueue.startStroke(strokeStrategy);
0217     processQueues();
0218     return id;
0219 }
0220 
0221 void KisUpdateScheduler::addJob(KisStrokeId id, KisStrokeJobData *data)
0222 {
0223     m_d->strokesQueue.addJob(id, data);
0224     processQueues();
0225 }
0226 
0227 void KisUpdateScheduler::endStroke(KisStrokeId id)
0228 {
0229     m_d->strokesQueue.endStroke(id);
0230     processQueues();
0231 }
0232 
0233 bool KisUpdateScheduler::cancelStroke(KisStrokeId id)
0234 {
0235     bool result = m_d->strokesQueue.cancelStroke(id);
0236     processQueues();
0237     return result;
0238 }
0239 
0240 bool KisUpdateScheduler::tryCancelCurrentStrokeAsync()
0241 {
0242     return m_d->strokesQueue.tryCancelCurrentStrokeAsync();
0243 }
0244 
0245 UndoResult KisUpdateScheduler::tryUndoLastStrokeAsync()
0246 {
0247     return m_d->strokesQueue.tryUndoLastStrokeAsync();
0248 }
0249 
0250 bool KisUpdateScheduler::wrapAroundModeSupported() const
0251 {
0252     return m_d->strokesQueue.wrapAroundModeSupported();
0253 }
0254 
0255 void KisUpdateScheduler::setLodPreferences(const KisLodPreferences &value)
0256 {
0257     m_d->strokesQueue.setLodPreferences(value);
0258 
0259     /**
0260      * The queue might have started an internal stroke for
0261      * cache synchronization. Process the queues to execute
0262      * it if needed.
0263      */
0264     processQueues();
0265 }
0266 
0267 KisLodPreferences KisUpdateScheduler::lodPreferences() const
0268 {
0269     return m_d->strokesQueue.lodPreferences();
0270 }
0271 
0272 void KisUpdateScheduler::explicitRegenerateLevelOfDetail()
0273 {
0274     m_d->strokesQueue.explicitRegenerateLevelOfDetail();
0275 
0276     // \see a comment in setDesiredLevelOfDetail()
0277     processQueues();
0278 }
0279 
0280 int KisUpdateScheduler::currentLevelOfDetail() const
0281 {
0282     int levelOfDetail = m_d->updaterContext.currentLevelOfDetail();
0283 
0284     if (levelOfDetail < 0) {
0285         // it is safe, because is called iff updaterContext has no running jobs at all
0286         levelOfDetail = m_d->updatesQueue.overrideLevelOfDetail();
0287     }
0288 
0289     if (levelOfDetail < 0) {
0290         levelOfDetail = 0;
0291     }
0292 
0293     return levelOfDetail;
0294 }
0295 
0296 void KisUpdateScheduler::setLod0ToNStrokeStrategyFactory(const KisLodSyncStrokeStrategyFactory &factory)
0297 {
0298     m_d->strokesQueue.setLod0ToNStrokeStrategyFactory(factory);
0299 }
0300 
0301 void KisUpdateScheduler::setSuspendResumeUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyPairFactory &factory)
0302 {
0303     m_d->strokesQueue.setSuspendResumeUpdatesStrokeStrategyFactory(factory);
0304 }
0305 
0306 void KisUpdateScheduler::setPurgeRedoStateCallback(const std::function<void ()> &callback)
0307 {
0308     m_d->strokesQueue.setPurgeRedoStateCallback(callback);
0309 }
0310 
0311 void KisUpdateScheduler::setPostSyncLod0GUIPlaneRequestForResumeCallback(const std::function<void ()> &callback)
0312 {
0313     m_d->strokesQueue.setPostSyncLod0GUIPlaneRequestForResumeCallback(callback);
0314 }
0315 
0316 KisPostExecutionUndoAdapter *KisUpdateScheduler::lodNPostExecutionUndoAdapter() const
0317 {
0318     return m_d->strokesQueue.lodNPostExecutionUndoAdapter();
0319 }
0320 
0321 void KisUpdateScheduler::updateSettings()
0322 {
0323     m_d->updatesQueue.updateSettings();
0324     KisImageConfig config(true);
0325     m_d->defaultBalancingRatio = config.schedulerBalancingRatio();
0326     setThreadsLimit(config.maxNumberOfThreads());
0327 }
0328 
0329 void KisUpdateScheduler::immediateLockForReadOnly()
0330 {
0331     m_d->processingBlocked = true;
0332     m_d->updaterContext.waitForDone();
0333 }
0334 
0335 void KisUpdateScheduler::unlock(bool resetLodLevels)
0336 {
0337     if (resetLodLevels) {
0338         /**
0339          * Legacy strokes may have changed the image while we didn't
0340          * control it. Notify the queue to take it into account.
0341          */
0342         m_d->strokesQueue.notifyUFOChangedImage();
0343     }
0344 
0345     m_d->processingBlocked = false;
0346     processQueues();
0347 }
0348 
0349 bool KisUpdateScheduler::isIdle()
0350 {
0351     bool result = false;
0352 
0353     if (tryBarrierLock()) {
0354         result = true;
0355         unlock(false);
0356     }
0357 
0358     return result;
0359 }
0360 
0361 void KisUpdateScheduler::waitForDone()
0362 {
0363     do {
0364         processQueues();
0365         m_d->updaterContext.waitForDone();
0366     } while(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty());
0367 }
0368 
0369 bool KisUpdateScheduler::tryBarrierLock()
0370 {
0371     if(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()) {
0372         return false;
0373     }
0374 
0375     m_d->processingBlocked = true;
0376     m_d->updaterContext.waitForDone();
0377     if(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()) {
0378         m_d->processingBlocked = false;
0379         processQueues();
0380         return false;
0381     }
0382 
0383     return true;
0384 }
0385 
0386 void KisUpdateScheduler::barrierLock()
0387 {
0388     do {
0389         m_d->processingBlocked = false;
0390         processQueues();
0391         m_d->processingBlocked = true;
0392         m_d->updaterContext.waitForDone();
0393     } while(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty());
0394 }
0395 
0396 void KisUpdateScheduler::processQueues()
0397 {
0398     wakeUpWaitingThreads();
0399 
0400     if(m_d->processingBlocked) return;
0401 
0402     if(m_d->strokesQueue.needsExclusiveAccess()) {
0403         DEBUG_BALANCING_METRICS("STROKES", "X");
0404         m_d->strokesQueue.processQueue(m_d->updaterContext,
0405                                         !m_d->updatesQueue.isEmpty());
0406 
0407         if(!m_d->strokesQueue.needsExclusiveAccess()) {
0408             tryProcessUpdatesQueue();
0409         }
0410     }
0411     else if(m_d->balancingRatio() * m_d->strokesQueue.sizeMetric() > m_d->updatesQueue.sizeMetric()) {
0412         DEBUG_BALANCING_METRICS("STROKES", "N");
0413         m_d->strokesQueue.processQueue(m_d->updaterContext,
0414                                         !m_d->updatesQueue.isEmpty());
0415         tryProcessUpdatesQueue();
0416     }
0417     else {
0418         DEBUG_BALANCING_METRICS("UPDATES", "N");
0419         tryProcessUpdatesQueue();
0420         m_d->strokesQueue.processQueue(m_d->updaterContext,
0421                                         !m_d->updatesQueue.isEmpty());
0422 
0423     }
0424 
0425     progressUpdate();
0426 }
0427 
0428 void KisUpdateScheduler::blockUpdates()
0429 {
0430     m_d->updatesFinishedCondition.initWaiting();
0431 
0432     m_d->updatesLockCounter.ref();
0433     while(haveUpdatesRunning()) {
0434         m_d->updatesFinishedCondition.wait();
0435     }
0436 
0437     m_d->updatesFinishedCondition.endWaiting();
0438 }
0439 
0440 void KisUpdateScheduler::unblockUpdates()
0441 {
0442     m_d->updatesLockCounter.deref();
0443     processQueues();
0444 }
0445 
0446 void KisUpdateScheduler::wakeUpWaitingThreads()
0447 {
0448     if(m_d->updatesLockCounter && !haveUpdatesRunning()) {
0449         m_d->updatesFinishedCondition.wakeAll();
0450     }
0451 }
0452 
0453 void KisUpdateScheduler::tryProcessUpdatesQueue()
0454 {
0455     QReadLocker locker(&m_d->updatesStartLock);
0456     if(m_d->updatesLockCounter) return;
0457 
0458     m_d->updatesQueue.processQueue(m_d->updaterContext);
0459 }
0460 
0461 bool KisUpdateScheduler::haveUpdatesRunning()
0462 {
0463     QWriteLocker locker(&m_d->updatesStartLock);
0464 
0465     qint32 numMergeJobs, numStrokeJobs;
0466     m_d->updaterContext.getJobsSnapshot(numMergeJobs, numStrokeJobs);
0467 
0468     return numMergeJobs;
0469 }
0470 
0471 void KisUpdateScheduler::continueUpdate(const QRect &rect)
0472 {
0473     Q_ASSERT(m_d->projectionUpdateListener);
0474     m_d->projectionUpdateListener->notifyProjectionUpdated(rect);
0475 }
0476 
0477 void KisUpdateScheduler::doSomeUsefulWork()
0478 {
0479     m_d->updatesQueue.optimize();
0480 }
0481 
0482 void KisUpdateScheduler::spareThreadAppeared()
0483 {
0484     processQueues();
0485 }
0486 
0487 KisTestableUpdateScheduler::KisTestableUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener,
0488                                                        qint32 threadCount)
0489 {
0490     updateSettings();
0491     m_d->projectionUpdateListener = projectionUpdateListener;
0492 
0493     setThreadsLimit(threadCount);
0494     m_d->updaterContext.setTestingMode(true);
0495 
0496     connectSignals();
0497 }
0498 
0499 KisUpdaterContext *KisTestableUpdateScheduler::updaterContext()
0500 {
0501     return &m_d->updaterContext;
0502 }