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

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