File indexing completed on 2024-05-12 15:58:44

0001 /*
0002  *  SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_strokes_queue.h"
0008 
0009 #include <QQueue>
0010 #include <QMutex>
0011 #include <QMutexLocker>
0012 #include "kis_stroke.h"
0013 #include "kis_updater_context.h"
0014 #include "kis_stroke_job_strategy.h"
0015 #include "kis_stroke_strategy.h"
0016 #include "kis_undo_stores.h"
0017 #include "kis_post_execution_undo_adapter.h"
0018 #include "KisCppQuirks.h"
0019 
0020 typedef QQueue<KisStrokeSP> StrokesQueue;
0021 typedef QQueue<KisStrokeSP>::iterator StrokesQueueIterator;
0022 
0023 #include "kis_image_interfaces.h"
0024 class KisStrokesQueue::LodNUndoStrokesFacade : public KisStrokesFacade
0025 {
0026 public:
0027     LodNUndoStrokesFacade(KisStrokesQueue *_q) : q(_q) {}
0028 
0029     KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override {
0030         return q->startLodNUndoStroke(strokeStrategy);
0031     }
0032 
0033     void addJob(KisStrokeId id, KisStrokeJobData *data) override {
0034         KisStrokeSP stroke = id.toStrongRef();
0035         KIS_SAFE_ASSERT_RECOVER_NOOP(stroke);
0036         KIS_SAFE_ASSERT_RECOVER_NOOP(!stroke->lodBuddy());
0037         KIS_SAFE_ASSERT_RECOVER_NOOP(stroke->type() == KisStroke::LODN);
0038 
0039         q->addJob(id, data);
0040     }
0041 
0042     void endStroke(KisStrokeId id) override {
0043         KisStrokeSP stroke = id.toStrongRef();
0044         KIS_SAFE_ASSERT_RECOVER_NOOP(stroke);
0045         KIS_SAFE_ASSERT_RECOVER_NOOP(!stroke->lodBuddy());
0046         KIS_SAFE_ASSERT_RECOVER_NOOP(stroke->type() == KisStroke::LODN);
0047 
0048         q->endStroke(id);
0049     }
0050 
0051     bool cancelStroke(KisStrokeId id) override {
0052         Q_UNUSED(id);
0053         qFatal("Not implemented");
0054         return false;
0055     }
0056 
0057 private:
0058     KisStrokesQueue *q;
0059 };
0060 
0061 
0062 struct Q_DECL_HIDDEN KisStrokesQueue::Private {
0063     Private(KisStrokesQueue *_q)
0064         : q(_q),
0065           openedStrokesCounter(0),
0066           needsExclusiveAccess(false),
0067           wrapAroundModeSupported(false),
0068           balancingRatioOverride(-1.0),
0069           currentStrokeLoaded(false),
0070           lodNNeedsSynchronization(true),
0071           desiredLevelOfDetail(0),
0072           nextDesiredLevelOfDetail(0),
0073           lodNStrokesFacade(_q),
0074           lodNPostExecutionUndoAdapter(&lodNUndoStore, &lodNStrokesFacade) {}
0075 
0076     KisStrokesQueue *q;
0077     StrokesQueue strokesQueue;
0078     int openedStrokesCounter;
0079     bool needsExclusiveAccess;
0080     bool wrapAroundModeSupported;
0081     qreal balancingRatioOverride;
0082     bool currentStrokeLoaded;
0083 
0084     bool lodNNeedsSynchronization;
0085     int desiredLevelOfDetail;
0086     int nextDesiredLevelOfDetail;
0087     QMutex mutex;
0088     KisLodSyncStrokeStrategyFactory lod0ToNStrokeStrategyFactory;
0089     KisSuspendResumeStrategyPairFactory suspendResumeUpdatesStrokeStrategyFactory;
0090     std::function<void()> purgeRedoStateCallback;
0091     KisSurrogateUndoStore lodNUndoStore;
0092     LodNUndoStrokesFacade lodNStrokesFacade;
0093     KisPostExecutionUndoAdapter lodNPostExecutionUndoAdapter;
0094     KisLodPreferences lodPreferences;
0095 
0096     void cancelForgettableStrokes();
0097     void startLod0ToNStroke(int levelOfDetail, bool forgettable);
0098 
0099 
0100     std::pair<StrokesQueueIterator, StrokesQueueIterator> currentLodRange();
0101     StrokesQueueIterator findNewLod0Pos();
0102     StrokesQueueIterator findNewLodNPos(KisStrokeSP lodN);
0103     bool shouldWrapInSuspendUpdatesStroke();
0104 
0105     void switchDesiredLevelOfDetail(bool forced);
0106     bool hasUnfinishedStrokes() const;
0107     void tryClearUndoOnStrokeCompletion(KisStrokeSP finishingStroke);
0108     void forceResetLodAndCloseCurrentLodRange();
0109     void loadStroke(KisStrokeSP stroke);
0110 };
0111 
0112 
0113 KisStrokesQueue::KisStrokesQueue()
0114   : m_d(new Private(this))
0115 {
0116 }
0117 
0118 KisStrokesQueue::~KisStrokesQueue()
0119 {
0120     Q_FOREACH (KisStrokeSP stroke, m_d->strokesQueue) {
0121         stroke->cancelStroke();
0122     }
0123 
0124     delete m_d;
0125 }
0126 
0127 template <class StrokePair, class StrokesQueue>
0128 typename StrokesQueue::iterator
0129 executeStrokePair(const StrokePair &pair, StrokesQueue &queue, typename StrokesQueue::iterator it, KisStroke::Type type, int levelOfDetail, KisStrokesQueueMutatedJobInterface *mutatedJobsInterface) {
0130     KisStrokeStrategy *strategy = pair.first;
0131     QList<KisStrokeJobData*> jobsData = pair.second;
0132 
0133     KisStrokeSP stroke(new KisStroke(strategy, type, levelOfDetail));
0134     strategy->setMutatedJobsInterface(mutatedJobsInterface, stroke);
0135     it = queue.insert(it, stroke);
0136     Q_FOREACH (KisStrokeJobData *jobData, jobsData) {
0137         stroke->addJob(jobData);
0138     }
0139     stroke->endStroke();
0140 
0141     return it;
0142 }
0143 
0144 void KisStrokesQueue::Private::startLod0ToNStroke(int levelOfDetail, bool forgettable)
0145 {
0146     // precondition: lock held!
0147     // precondition: lod > 0
0148     KIS_ASSERT_RECOVER_RETURN(levelOfDetail);
0149 
0150     {
0151         // sanity check: there should be no open LoD range now!
0152         StrokesQueueIterator it;
0153         StrokesQueueIterator end;
0154         std::tie(it, end) = currentLodRange();
0155         KIS_SAFE_ASSERT_RECOVER_NOOP(it == end);
0156     }
0157 
0158     if (!this->lod0ToNStrokeStrategyFactory) return;
0159 
0160     KisLodSyncPair syncPair = this->lod0ToNStrokeStrategyFactory(forgettable);
0161     executeStrokePair(syncPair, this->strokesQueue, this->strokesQueue.end(),  KisStroke::LODN, levelOfDetail, q);
0162 
0163     this->lodNNeedsSynchronization = false;
0164 }
0165 
0166 void KisStrokesQueue::Private::cancelForgettableStrokes()
0167 {
0168     if (!strokesQueue.isEmpty() && !hasUnfinishedStrokes()) {
0169         Q_FOREACH (KisStrokeSP stroke, strokesQueue) {
0170             KIS_ASSERT_RECOVER_NOOP(stroke->isEnded());
0171 
0172             if (stroke->canForgetAboutMe()) {
0173                 stroke->cancelStroke();
0174             }
0175         }
0176     }
0177 }
0178 
0179 std::pair<StrokesQueueIterator, StrokesQueueIterator> KisStrokesQueue::Private::currentLodRange()
0180 {
0181     /**
0182      * LoD-capable strokes should live in the range after the last
0183      * legacy stroke of the queue
0184      */
0185 
0186     for (auto it = std::make_reverse_iterator(strokesQueue.end());
0187          it != std::make_reverse_iterator(strokesQueue.begin());
0188          ++it) {
0189 
0190         if ((*it)->type() == KisStroke::LEGACY) {
0191             // it.base() returns the next element after the one we found
0192             return std::make_pair(it.base(), strokesQueue.end());
0193         }
0194     }
0195 
0196     return std::make_pair(strokesQueue.begin(), strokesQueue.end());
0197 }
0198 
0199 bool KisStrokesQueue::Private::shouldWrapInSuspendUpdatesStroke()
0200 {
0201     StrokesQueueIterator it;
0202     StrokesQueueIterator end;
0203     std::tie(it, end) = currentLodRange();
0204 
0205     for (; it != end; ++it) {
0206         KisStrokeSP stroke = *it;
0207 
0208         if (stroke->isCancelled()) continue;
0209 
0210         if (stroke->type() == KisStroke::RESUME) {
0211             return false;
0212         }
0213     }
0214 
0215     return true;
0216 }
0217 
0218 StrokesQueueIterator KisStrokesQueue::Private::findNewLod0Pos()
0219 {
0220     StrokesQueueIterator it;
0221     StrokesQueueIterator end;
0222     std::tie(it, end) = currentLodRange();
0223 
0224     for (; it != end; ++it) {
0225         if ((*it)->isCancelled()) continue;
0226 
0227         if ((*it)->type() == KisStroke::RESUME) {
0228             return it;
0229         }
0230     }
0231 
0232     return it;
0233 }
0234 
0235 StrokesQueueIterator KisStrokesQueue::Private::findNewLodNPos(KisStrokeSP lodN)
0236 {
0237     StrokesQueueIterator it;
0238     StrokesQueueIterator end;
0239     std::tie(it, end) = currentLodRange();
0240 
0241     for (; it != end; ++it) {
0242         if ((*it)->isCancelled()) continue;
0243 
0244         if ((*it)->type() == KisStroke::LOD0 ||
0245             (*it)->type() == KisStroke::SUSPEND ||
0246             (*it)->type() == KisStroke::RESUME) {
0247 
0248             if (it != end && it == strokesQueue.begin()) {
0249                 KisStrokeSP head = *it;
0250 
0251                 if (head->supportsSuspension()) {
0252                     head->suspendStroke(lodN);
0253                 }
0254             }
0255 
0256             return it;
0257         }
0258     }
0259 
0260     return it;
0261 }
0262 
0263 KisStrokeId KisStrokesQueue::startLodNUndoStroke(KisStrokeStrategy *strokeStrategy)
0264 {
0265     QMutexLocker locker(&m_d->mutex);
0266 
0267     KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->lodNNeedsSynchronization);
0268     KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->desiredLevelOfDetail > 0);
0269 
0270     KisStrokeSP buddy(new KisStroke(strokeStrategy, KisStroke::LODN, m_d->desiredLevelOfDetail));
0271     strokeStrategy->setMutatedJobsInterface(this, buddy);
0272     m_d->strokesQueue.insert(m_d->findNewLodNPos(buddy), buddy);
0273 
0274     KisStrokeId id(buddy);
0275     m_d->openedStrokesCounter++;
0276 
0277     return id;
0278 }
0279 
0280 KisStrokeId KisStrokesQueue::startStroke(KisStrokeStrategy *strokeStrategy)
0281 {
0282     QMutexLocker locker(&m_d->mutex);
0283 
0284     KisStrokeSP stroke;
0285     KisStrokeStrategy* lodBuddyStrategy;
0286 
0287     // we should let forgettable strokes to queue up
0288     if (!strokeStrategy->canForgetAboutMe()) {
0289         m_d->cancelForgettableStrokes();
0290     }
0291 
0292     if (m_d->desiredLevelOfDetail &&
0293         (m_d->lodPreferences.lodPreferred() || strokeStrategy->forceLodModeIfPossible()) &&
0294         (lodBuddyStrategy =
0295          strokeStrategy->createLodClone(m_d->desiredLevelOfDetail))) {
0296 
0297         if (m_d->lodNNeedsSynchronization) {
0298             m_d->startLod0ToNStroke(m_d->desiredLevelOfDetail, false);
0299         }
0300 
0301         stroke = KisStrokeSP(new KisStroke(strokeStrategy, KisStroke::LOD0, 0));
0302 
0303         KisStrokeSP buddy(new KisStroke(lodBuddyStrategy, KisStroke::LODN, m_d->desiredLevelOfDetail));
0304         lodBuddyStrategy->setMutatedJobsInterface(this, buddy);
0305         stroke->setLodBuddy(buddy);
0306         m_d->strokesQueue.insert(m_d->findNewLodNPos(buddy), buddy);
0307 
0308         if (m_d->shouldWrapInSuspendUpdatesStroke()) {
0309 
0310             KisSuspendResumePair suspendPair;
0311             KisSuspendResumePair resumePair;
0312             std::tie(suspendPair, resumePair) = m_d->suspendResumeUpdatesStrokeStrategyFactory();
0313 
0314             StrokesQueueIterator it = m_d->findNewLod0Pos();
0315 
0316             it = executeStrokePair(resumePair, m_d->strokesQueue, it, KisStroke::RESUME, 0, this);
0317             it = m_d->strokesQueue.insert(it, stroke);
0318             it = executeStrokePair(suspendPair, m_d->strokesQueue, it, KisStroke::SUSPEND, 0, this);
0319 
0320         } else {
0321             m_d->strokesQueue.insert(m_d->findNewLod0Pos(), stroke);
0322         }
0323 
0324     } else {
0325         stroke = KisStrokeSP(new KisStroke(strokeStrategy, KisStroke::LEGACY, 0));
0326         m_d->strokesQueue.enqueue(stroke);
0327     }
0328 
0329     KisStrokeId id(stroke);
0330     strokeStrategy->setMutatedJobsInterface(this, id);
0331 
0332     m_d->openedStrokesCounter++;
0333 
0334     if (stroke->type() == KisStroke::LEGACY) {
0335         m_d->lodNNeedsSynchronization = true;
0336     }
0337 
0338     return id;
0339 }
0340 
0341 void KisStrokesQueue::addJob(KisStrokeId id, KisStrokeJobData *data)
0342 {
0343     QMutexLocker locker(&m_d->mutex);
0344 
0345     KisStrokeSP stroke = id.toStrongRef();
0346     KIS_SAFE_ASSERT_RECOVER_RETURN(stroke);
0347 
0348     KisStrokeSP buddy = stroke->lodBuddy();
0349     if (buddy) {
0350         KisStrokeJobData *clonedData =
0351             data->createLodClone(buddy->worksOnLevelOfDetail());
0352         KIS_ASSERT_RECOVER_RETURN(clonedData);
0353 
0354         buddy->addJob(clonedData);
0355     }
0356 
0357     stroke->addJob(data);
0358 }
0359 
0360 void KisStrokesQueue::addMutatedJobs(KisStrokeId id, const QVector<KisStrokeJobData *> list)
0361 {
0362     QMutexLocker locker(&m_d->mutex);
0363 
0364     KisStrokeSP stroke = id.toStrongRef();
0365     KIS_SAFE_ASSERT_RECOVER_RETURN(stroke);
0366 
0367     stroke->addMutatedJobs(list);
0368 }
0369 
0370 void KisStrokesQueue::endStroke(KisStrokeId id)
0371 {
0372     QMutexLocker locker(&m_d->mutex);
0373 
0374     KisStrokeSP stroke = id.toStrongRef();
0375     KIS_SAFE_ASSERT_RECOVER_RETURN(stroke);
0376     stroke->endStroke();
0377     m_d->openedStrokesCounter--;
0378 
0379     KisStrokeSP buddy = stroke->lodBuddy();
0380     if (buddy) {
0381         buddy->endStroke();
0382     }
0383 }
0384 
0385 bool KisStrokesQueue::cancelStroke(KisStrokeId id)
0386 {
0387     QMutexLocker locker(&m_d->mutex);
0388 
0389     KisStrokeSP stroke = id.toStrongRef();
0390     if(stroke) {
0391         stroke->cancelStroke();
0392         m_d->openedStrokesCounter--;
0393 
0394         KisStrokeSP buddy = stroke->lodBuddy();
0395         if (buddy) {
0396             buddy->cancelStroke();
0397         }
0398     }
0399     return stroke;
0400 }
0401 
0402 bool KisStrokesQueue::Private::hasUnfinishedStrokes() const
0403 {
0404     Q_FOREACH (KisStrokeSP stroke, strokesQueue) {
0405         if (!stroke->isEnded()) {
0406             return true;
0407         }
0408     }
0409 
0410     return false;
0411 }
0412 
0413 bool KisStrokesQueue::tryCancelCurrentStrokeAsync()
0414 {
0415     bool anythingCanceled = false;
0416 
0417     QMutexLocker locker(&m_d->mutex);
0418 
0419     /**
0420      * We cancel only ended strokes. This is done to avoid
0421      * handling dangling pointers problem (KisStrokeId). The owner
0422      * of a stroke will cancel the stroke itself if needed.
0423      */
0424     if (!m_d->strokesQueue.isEmpty() &&
0425         !m_d->hasUnfinishedStrokes()) {
0426 
0427         /**
0428          * 1) We can cancel only cancellable strokes
0429          * 2) We can only cancel a continuos set of strokes
0430          *
0431          * Basically, a non-cancellable stroke adds a barrier
0432          * wall into the strokes queue, preventing cancellation
0433          * of any strokes that we added into the queue earlier.
0434          */
0435         const auto lastNonCancellableStrokeIt =
0436             std::find_if_not(std::make_reverse_iterator(m_d->strokesQueue.end()),
0437                              std::make_reverse_iterator(m_d->strokesQueue.begin()),
0438                              std::mem_fn(&KisStroke::isAsynchronouslyCancellable));
0439 
0440         bool needsLodNSynchronization = false;
0441 
0442         for (auto it = lastNonCancellableStrokeIt.base(); it != m_d->strokesQueue.end(); it++) {
0443             KisStrokeSP currentStroke = *it;
0444             KIS_ASSERT_RECOVER_NOOP(currentStroke->isEnded());
0445             KIS_ASSERT_RECOVER_NOOP(currentStroke->isAsynchronouslyCancellable());
0446 
0447             currentStroke->cancelStroke();
0448             anythingCanceled = true;
0449 
0450             // we shouldn't cancel buddies...
0451             if (currentStroke->type() == KisStroke::LOD0) {
0452                 /**
0453                  * If the buddy has already finished, we cannot undo it because
0454                  * it doesn't store any undo data. Therefore we just regenerate
0455                  * the LOD caches.
0456                  */
0457                 needsLodNSynchronization = true;
0458             }
0459 
0460         }
0461 
0462         if (needsLodNSynchronization) {
0463             m_d->forceResetLodAndCloseCurrentLodRange();
0464         }
0465     }
0466 
0467     /**
0468      * NOTE: We do not touch the openedStrokesCounter here since
0469      *       we work with closed id's only here
0470      */
0471 
0472     return anythingCanceled;
0473 }
0474 
0475 UndoResult KisStrokesQueue::tryUndoLastStrokeAsync()
0476 {
0477     UndoResult result = UNDO_FAIL;
0478 
0479     QMutexLocker locker(&m_d->mutex);
0480 
0481     KisStrokeSP lastStroke;
0482     KisStrokeSP lastBuddy;
0483     bool buddyFound = false;
0484 
0485     for (auto it = std::make_reverse_iterator(m_d->strokesQueue.constEnd());
0486          it != std::make_reverse_iterator(m_d->strokesQueue.constBegin());
0487          ++it) {
0488 
0489         if ((*it)->type() == KisStroke::LEGACY) {
0490             break;
0491         }
0492 
0493         if (!lastStroke && (*it)->type() == KisStroke::LOD0 && !(*it)->isCancelled()) {
0494             lastStroke = *it;
0495             lastBuddy = lastStroke->lodBuddy();
0496 
0497             KIS_SAFE_ASSERT_RECOVER(lastBuddy) {
0498                 lastStroke.clear();
0499                 lastBuddy.clear();
0500                 break;
0501             }
0502         }
0503 
0504         KIS_SAFE_ASSERT_RECOVER(!lastStroke || *it == lastBuddy || (*it)->type() != KisStroke::LODN) {
0505             lastStroke.clear();
0506             lastBuddy.clear();
0507             break;
0508         }
0509 
0510         if (lastStroke && *it == lastBuddy) {
0511             KIS_SAFE_ASSERT_RECOVER(lastBuddy->type() == KisStroke::LODN) {
0512                 lastStroke.clear();
0513                 lastBuddy.clear();
0514                 break;
0515             }
0516             buddyFound = true;
0517             break;
0518         }
0519     }
0520 
0521     if (!lastStroke) return UNDO_FAIL;
0522     if (!lastStroke->isEnded()) return UNDO_FAIL;
0523     if (lastStroke->isCancelled()) return UNDO_FAIL;
0524 
0525     KIS_SAFE_ASSERT_RECOVER_NOOP(!buddyFound ||
0526                                  lastStroke->isCancelled() == lastBuddy->isCancelled());
0527     KIS_SAFE_ASSERT_RECOVER_NOOP(lastBuddy->isEnded());
0528 
0529     if (!lastStroke->canCancel()) {
0530         return UNDO_WAIT;
0531     }
0532     lastStroke->cancelStroke();
0533 
0534     if (buddyFound && lastBuddy->canCancel()) {
0535         lastBuddy->cancelStroke();
0536     } else {
0537         // TODO: assert that checks that there is no other lodn strokes
0538         locker.unlock();
0539         m_d->lodNUndoStore.undo();
0540         m_d->lodNUndoStore.purgeRedoState();
0541         locker.relock();
0542     }
0543 
0544     result = UNDO_OK;
0545 
0546     return result;
0547 }
0548 
0549 void KisStrokesQueue::Private::tryClearUndoOnStrokeCompletion(KisStrokeSP finishingStroke)
0550 {
0551     if (finishingStroke->type() != KisStroke::RESUME) return;
0552 
0553     bool hasResumeStrokes = false;
0554     bool hasLod0Strokes = false;
0555 
0556     auto it = std::find(strokesQueue.begin(), strokesQueue.end(), finishingStroke);
0557     KIS_SAFE_ASSERT_RECOVER_RETURN(it != strokesQueue.end());
0558     ++it;
0559 
0560     for (; it != strokesQueue.end(); ++it) {
0561         KisStrokeSP stroke = *it;
0562         if (stroke->type() == KisStroke::LEGACY) break;
0563 
0564         hasLod0Strokes |= stroke->type() == KisStroke::LOD0;
0565         hasResumeStrokes |= stroke->type() == KisStroke::RESUME;
0566     }
0567 
0568     KIS_SAFE_ASSERT_RECOVER_NOOP(!hasLod0Strokes || hasResumeStrokes);
0569 
0570     if (!hasResumeStrokes && !hasLod0Strokes) {
0571         lodNUndoStore.clear();
0572     }
0573 }
0574 
0575 void KisStrokesQueue::Private::forceResetLodAndCloseCurrentLodRange()
0576 {
0577     lodNNeedsSynchronization = true;
0578 
0579     if (!strokesQueue.isEmpty() && strokesQueue.last()->type() != KisStroke::LEGACY) {
0580 
0581         std::pair<KisStrokeStrategy*, QList<KisStrokeJobData*>> fakeStrokePair
0582                 (new KisStrokeStrategy(QLatin1String("fake_sync")), {});
0583 
0584         executeStrokePair(fakeStrokePair, this->strokesQueue, this->strokesQueue.end(),  KisStroke::LEGACY, 0, q);
0585     }
0586 }
0587 
0588 void KisStrokesQueue::processQueue(KisUpdaterContext &updaterContext,
0589                                    bool externalJobsPending)
0590 {
0591     updaterContext.lock();
0592     m_d->mutex.lock();
0593 
0594     while(updaterContext.hasSpareThread() &&
0595           processOneJob(updaterContext,
0596                         externalJobsPending));
0597 
0598     m_d->mutex.unlock();
0599     updaterContext.unlock();
0600 }
0601 
0602 bool KisStrokesQueue::needsExclusiveAccess() const
0603 {
0604     return m_d->needsExclusiveAccess;
0605 }
0606 
0607 bool KisStrokesQueue::wrapAroundModeSupported() const
0608 {
0609     return m_d->wrapAroundModeSupported;
0610 }
0611 
0612 qreal KisStrokesQueue::balancingRatioOverride() const
0613 {
0614     return m_d->balancingRatioOverride;
0615 }
0616 
0617 KisLodPreferences KisStrokesQueue::lodPreferences() const
0618 {
0619     QMutexLocker locker(&m_d->mutex);
0620 
0621     /**
0622      * The desired level of detail might have not been activated due to
0623      * multi-stage activation process
0624      */
0625     return KisLodPreferences(m_d->lodPreferences.flags(), m_d->desiredLevelOfDetail);
0626 }
0627 
0628 void KisStrokesQueue::setLodPreferences(const KisLodPreferences &value)
0629 {
0630     QMutexLocker locker(&m_d->mutex);
0631 
0632     m_d->lodPreferences = value;
0633 
0634     if (m_d->lodPreferences.desiredLevelOfDetail() != m_d->nextDesiredLevelOfDetail ||
0635             (m_d->lodPreferences.lodPreferred() && m_d->lodNNeedsSynchronization)) {
0636 
0637         m_d->nextDesiredLevelOfDetail = m_d->lodPreferences.desiredLevelOfDetail();
0638         m_d->switchDesiredLevelOfDetail(false);
0639     }
0640 }
0641 
0642 bool KisStrokesQueue::isEmpty() const
0643 {
0644     QMutexLocker locker(&m_d->mutex);
0645     return m_d->strokesQueue.isEmpty();
0646 }
0647 
0648 qint32 KisStrokesQueue::sizeMetric() const
0649 {
0650     QMutexLocker locker(&m_d->mutex);
0651     if(m_d->strokesQueue.isEmpty()) return 0;
0652 
0653     // just a rough approximation
0654     return qMax(1, m_d->strokesQueue.head()->numJobs()) * m_d->strokesQueue.size();
0655 }
0656 
0657 void KisStrokesQueue::Private::switchDesiredLevelOfDetail(bool forced)
0658 {
0659     if (forced || nextDesiredLevelOfDetail != desiredLevelOfDetail) {
0660         Q_FOREACH (KisStrokeSP stroke, strokesQueue) {
0661             if (stroke->type() != KisStroke::LEGACY)
0662                 return;
0663         }
0664 
0665         const bool forgettable =
0666             forced && !lodNNeedsSynchronization &&
0667             desiredLevelOfDetail == nextDesiredLevelOfDetail;
0668 
0669         desiredLevelOfDetail = nextDesiredLevelOfDetail;
0670         lodNNeedsSynchronization |= !forgettable;
0671 
0672         if (desiredLevelOfDetail && lodPreferences.lodPreferred()) {
0673             startLod0ToNStroke(desiredLevelOfDetail, forgettable);
0674         }
0675     }
0676 }
0677 
0678 void KisStrokesQueue::explicitRegenerateLevelOfDetail()
0679 {
0680     QMutexLocker locker(&m_d->mutex);
0681     m_d->switchDesiredLevelOfDetail(true);
0682 }
0683 
0684 void KisStrokesQueue::notifyUFOChangedImage()
0685 {
0686     QMutexLocker locker(&m_d->mutex);
0687 
0688     m_d->forceResetLodAndCloseCurrentLodRange();
0689 }
0690 
0691 void KisStrokesQueue::debugDumpAllStrokes()
0692 {
0693     QMutexLocker locker(&m_d->mutex);
0694 
0695     qDebug() <<"===";
0696     Q_FOREACH (KisStrokeSP stroke, m_d->strokesQueue) {
0697         qDebug() << ppVar(stroke->name()) << ppVar(stroke->type()) << ppVar(stroke->numJobs()) << ppVar(stroke->isInitialized()) << ppVar(stroke->isCancelled());
0698     }
0699     qDebug() <<"===";
0700 }
0701 
0702 void KisStrokesQueue::setLod0ToNStrokeStrategyFactory(const KisLodSyncStrokeStrategyFactory &factory)
0703 {
0704     m_d->lod0ToNStrokeStrategyFactory = factory;
0705 }
0706 
0707 void KisStrokesQueue::setSuspendResumeUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyPairFactory &factory)
0708 {
0709     m_d->suspendResumeUpdatesStrokeStrategyFactory = factory;
0710 }
0711 
0712 void KisStrokesQueue::setPurgeRedoStateCallback(const std::function<void ()> &callback)
0713 {
0714     m_d->purgeRedoStateCallback = callback;
0715 }
0716 
0717 KisPostExecutionUndoAdapter *KisStrokesQueue::lodNPostExecutionUndoAdapter() const
0718 {
0719     return &m_d->lodNPostExecutionUndoAdapter;
0720 }
0721 
0722 KUndo2MagicString KisStrokesQueue::currentStrokeName() const
0723 {
0724     QMutexLocker locker(&m_d->mutex);
0725     if(m_d->strokesQueue.isEmpty()) return KUndo2MagicString();
0726 
0727     return m_d->strokesQueue.head()->name();
0728 }
0729 
0730 bool KisStrokesQueue::hasOpenedStrokes() const
0731 {
0732     QMutexLocker locker(&m_d->mutex);
0733     return m_d->openedStrokesCounter;
0734 }
0735 
0736 bool KisStrokesQueue::processOneJob(KisUpdaterContext &updaterContext,
0737                                     bool externalJobsPending)
0738 {
0739     if(m_d->strokesQueue.isEmpty()) return false;
0740     bool result = false;
0741 
0742     const int levelOfDetail = updaterContext.currentLevelOfDetail();
0743 
0744     const KisUpdaterContextSnapshotEx snapshot = updaterContext.getContextSnapshotEx();
0745 
0746     const bool hasStrokeJobs = !(snapshot == ContextEmpty ||
0747                                  snapshot == HasMergeJob);
0748     const bool hasMergeJobs = snapshot & HasMergeJob;
0749 
0750     if(checkStrokeState(hasStrokeJobs, levelOfDetail) &&
0751        checkExclusiveProperty(hasMergeJobs, hasStrokeJobs) &&
0752        checkSequentialProperty(snapshot, externalJobsPending)) {
0753 
0754         KisStrokeSP stroke = m_d->strokesQueue.head();
0755         updaterContext.addStrokeJob(stroke->popOneJob());
0756         result = true;
0757     }
0758 
0759     return result;
0760 }
0761 
0762 void KisStrokesQueue::Private::loadStroke(KisStrokeSP stroke)
0763 {
0764     needsExclusiveAccess = stroke->isExclusive();
0765     wrapAroundModeSupported = stroke->supportsWrapAroundMode();
0766     balancingRatioOverride = stroke->balancingRatioOverride();
0767     currentStrokeLoaded = true;
0768 
0769     /**
0770      * Some of the strokes can cancel their work with undoing all the
0771      * changes they did to the paint devices. The problem is that undo
0772      * stack will know nothing about it. Therefore, just notify it
0773      * explicitly
0774      */
0775     if (purgeRedoStateCallback &&
0776         stroke->clearsRedoOnStart()) {
0777 
0778         purgeRedoStateCallback();
0779     }
0780 }
0781 
0782 bool KisStrokesQueue::checkStrokeState(bool hasStrokeJobsRunning,
0783                                        int runningLevelOfDetail)
0784 {
0785     KisStrokeSP stroke = m_d->strokesQueue.head();
0786     bool result = false;
0787 
0788     /**
0789      * We cannot start/continue a stroke if its LOD differs from
0790      * the one that is running on CPU
0791      */
0792     bool hasLodCompatibility = checkLevelOfDetailProperty(runningLevelOfDetail);
0793     bool hasJobs = stroke->hasJobs();
0794 
0795     /**
0796      * The stroke may be cancelled very fast. In this case it will
0797      * end up in the state:
0798      *
0799      * !stroke->isInitialized() && stroke->isEnded() && !stroke->hasJobs()
0800      *
0801      * This means that !isInitialised() doesn't imply there are any
0802      * jobs present.
0803      */
0804     if(!stroke->isInitialized() && hasJobs && hasLodCompatibility) {
0805         /**
0806          * It might happen that the stroke got initialized, but its job was not
0807          * started due to some other reasons like exclusivity. Therefore the
0808          * stroke might end up in loaded, but uninitialized state.
0809          */
0810         if (!m_d->currentStrokeLoaded) {
0811             m_d->loadStroke(stroke);
0812         }
0813 
0814         result = true;
0815     }
0816     else if(hasJobs && hasLodCompatibility) {
0817         /**
0818          * If the stroke has no initialization phase, then it can
0819          * arrive here unloaded.
0820          */
0821         if (!m_d->currentStrokeLoaded) {
0822             m_d->loadStroke(stroke);
0823         }
0824 
0825         result = true;
0826     }
0827     else if(stroke->isEnded() && !hasJobs && !hasStrokeJobsRunning) {
0828         m_d->tryClearUndoOnStrokeCompletion(stroke);
0829 
0830         m_d->strokesQueue.dequeue(); // deleted by shared pointer
0831         m_d->needsExclusiveAccess = false;
0832         m_d->wrapAroundModeSupported = false;
0833         m_d->balancingRatioOverride = -1.0;
0834         m_d->currentStrokeLoaded = false;
0835 
0836         m_d->switchDesiredLevelOfDetail(false);
0837 
0838         if(!m_d->strokesQueue.isEmpty()) {
0839             result = checkStrokeState(false, runningLevelOfDetail);
0840         }
0841     }
0842 
0843     return result;
0844 }
0845 
0846 bool KisStrokesQueue::checkExclusiveProperty(bool hasMergeJobs,
0847                                              bool hasStrokeJobs)
0848 {
0849     Q_UNUSED(hasStrokeJobs);
0850 
0851     if(!m_d->strokesQueue.head()->isExclusive()) return true;
0852     return hasMergeJobs == 0;
0853 }
0854 
0855 bool KisStrokesQueue::checkSequentialProperty(KisUpdaterContextSnapshotEx snapshot,
0856                                               bool externalJobsPending)
0857 {
0858     KisStrokeSP stroke = m_d->strokesQueue.head();
0859 
0860     if (snapshot & HasSequentialJob ||
0861         snapshot & HasBarrierJob) {
0862         return false;
0863     }
0864 
0865     KisStrokeJobData::Sequentiality nextSequentiality =
0866         stroke->nextJobSequentiality();
0867 
0868     if (nextSequentiality == KisStrokeJobData::UNIQUELY_CONCURRENT &&
0869         snapshot & HasUniquelyConcurrentJob) {
0870 
0871         return false;
0872     }
0873 
0874     if (nextSequentiality == KisStrokeJobData::SEQUENTIAL &&
0875         (snapshot & HasUniquelyConcurrentJob ||
0876          snapshot & HasConcurrentJob)) {
0877 
0878         return false;
0879     }
0880 
0881     if (nextSequentiality == KisStrokeJobData::BARRIER &&
0882         (snapshot & HasUniquelyConcurrentJob ||
0883          snapshot & HasConcurrentJob ||
0884          snapshot & HasMergeJob ||
0885          externalJobsPending)) {
0886 
0887         return false;
0888     }
0889 
0890     return true;
0891 }
0892 
0893 bool KisStrokesQueue::checkLevelOfDetailProperty(int runningLevelOfDetail)
0894 {
0895     KisStrokeSP stroke = m_d->strokesQueue.head();
0896 
0897     return runningLevelOfDetail < 0 ||
0898         stroke->nextJobLevelOfDetail() == runningLevelOfDetail;
0899 }