11#include <QMutexLocker>
53 qFatal(
"Not implemented");
65 openedStrokesCounter(0),
66 needsExclusiveAccess(false),
67 wrapAroundModeSupported(false),
68 balancingRatioOverride(-1.0),
69 currentStrokeLoaded(false),
70 lodNNeedsSynchronization(true),
71 desiredLevelOfDetail(0),
72 nextDesiredLevelOfDetail(0),
73 lodNStrokesFacade(_q),
74 lodNPostExecutionUndoAdapter(&lodNUndoStore, &lodNStrokesFacade) {}
122 stroke->cancelStroke();
128template <
class StrokePair,
class StrokesQueue>
129typename StrokesQueue::iterator
136 it = queue.insert(it, stroke);
138 stroke->addJob(jobData);
145void KisStrokesQueue::Private::startLod0ToNStroke(
int levelOfDetail,
bool forgettable)
155 std::tie(it, end) = currentLodRange();
159 if (!this->lod0ToNStrokeStrategyFactory)
return;
161 KisLodSyncPair syncPair = this->lod0ToNStrokeStrategyFactory(forgettable);
164 this->lodNNeedsSynchronization =
false;
167void KisStrokesQueue::Private::cancelForgettableStrokes()
169 if (!strokesQueue.isEmpty() && !hasUnfinishedStrokes()) {
173 if (stroke->canForgetAboutMe()) {
174 stroke->cancelStroke();
180std::pair<StrokesQueueIterator, StrokesQueueIterator> KisStrokesQueue::Private::currentLodRange()
187 for (
auto it = std::make_reverse_iterator(strokesQueue.end());
188 it != std::make_reverse_iterator(strokesQueue.begin());
193 return std::make_pair(it.base(), strokesQueue.end());
197 return std::make_pair(strokesQueue.begin(), strokesQueue.end());
200bool KisStrokesQueue::Private::shouldWrapInSuspendUpdatesStroke()
204 std::tie(it, end) = currentLodRange();
206 for (; it != end; ++it) {
209 if (stroke->isCancelled())
continue;
223 std::tie(it, end) = currentLodRange();
225 for (; it != end; ++it) {
226 if ((*it)->isCancelled())
continue;
240 std::tie(it, end) = currentLodRange();
242 for (; it != end; ++it) {
243 if ((*it)->isCancelled())
continue;
249 if (it != end && it == strokesQueue.begin()) {
252 if (head->supportsSuspension()) {
253 head->suspendStroke(lodN);
266 QMutexLocker locker(&
m_d->mutex);
273 m_d->strokesQueue.insert(
m_d->findNewLodNPos(buddy), buddy);
276 m_d->openedStrokesCounter++;
283 QMutexLocker locker(&
m_d->mutex);
290 m_d->cancelForgettableStrokes();
293 if (
m_d->desiredLevelOfDetail &&
298 if (
m_d->lodNNeedsSynchronization) {
299 m_d->startLod0ToNStroke(
m_d->desiredLevelOfDetail,
false);
306 stroke->setLodBuddy(buddy);
307 m_d->strokesQueue.insert(
m_d->findNewLodNPos(buddy), buddy);
309 if (
m_d->shouldWrapInSuspendUpdatesStroke()) {
313 std::tie(suspendPair, resumePair) =
m_d->suspendResumeUpdatesStrokeStrategyFactory();
318 it =
m_d->strokesQueue.insert(it, stroke);
322 m_d->strokesQueue.insert(
m_d->findNewLod0Pos(), stroke);
327 m_d->strokesQueue.enqueue(stroke);
333 m_d->openedStrokesCounter++;
336 m_d->lodNNeedsSynchronization =
true;
344 QMutexLocker locker(&
m_d->mutex);
355 buddy->addJob(clonedData);
358 stroke->addJob(data);
363 QMutexLocker locker(&
m_d->mutex);
368 stroke->addMutatedJobs(list);
373 QMutexLocker locker(&
m_d->mutex);
378 m_d->openedStrokesCounter--;
388 QMutexLocker locker(&
m_d->mutex);
392 stroke->cancelStroke();
393 m_d->openedStrokesCounter--;
397 buddy->cancelStroke();
403bool KisStrokesQueue::Private::hasUnfinishedStrokes()
const
406 if (!stroke->isEnded()) {
416 bool anythingCanceled =
false;
418 QMutexLocker locker(&
m_d->mutex);
425 if (!
m_d->strokesQueue.isEmpty() &&
426 !
m_d->hasUnfinishedStrokes()) {
436 const auto lastNonCancellableStrokeIt =
437 std::find_if_not(std::make_reverse_iterator(
m_d->strokesQueue.end()),
438 std::make_reverse_iterator(
m_d->strokesQueue.begin()),
441 bool needsLodNSynchronization =
false;
443 for (
auto it = lastNonCancellableStrokeIt.base(); it !=
m_d->strokesQueue.end(); it++) {
448 currentStroke->cancelStroke();
449 anythingCanceled =
true;
458 needsLodNSynchronization =
true;
463 if (needsLodNSynchronization) {
464 m_d->forceResetLodAndCloseCurrentLodRange();
473 return anythingCanceled;
480 QMutexLocker locker(&
m_d->mutex);
484 bool buddyFound =
false;
486 for (
auto it = std::make_reverse_iterator(
m_d->strokesQueue.constEnd());
487 it != std::make_reverse_iterator(
m_d->strokesQueue.constBegin());
494 if (!lastStroke && (*it)->type() ==
KisStroke::LOD0 && !(*it)->isCancelled()) {
496 lastBuddy = lastStroke->lodBuddy();
511 if (lastStroke && *it == lastBuddy) {
523 if (!lastStroke->isEnded())
return UNDO_FAIL;
524 if (lastStroke->isCancelled())
return UNDO_FAIL;
527 lastStroke->isCancelled() == lastBuddy->isCancelled());
530 if (!lastStroke->canCancel()) {
533 lastStroke->cancelStroke();
535 if (buddyFound && lastBuddy->canCancel()) {
536 lastBuddy->cancelStroke();
540 m_d->lodNUndoStore.undo();
541 m_d->lodNUndoStore.purgeRedoState();
550void KisStrokesQueue::Private::tryClearUndoOnStrokeCompletion(
KisStrokeSP finishingStroke)
554 bool hasResumeStrokes =
false;
555 bool hasLod0Strokes =
false;
557 auto it = std::find(strokesQueue.begin(), strokesQueue.end(), finishingStroke);
561 for (; it != strokesQueue.end(); ++it) {
571 if (!hasResumeStrokes && !hasLod0Strokes) {
572 lodNUndoStore.clear();
576void KisStrokesQueue::Private::forceResetLodAndCloseCurrentLodRange()
578 lodNNeedsSynchronization =
true;
580 if (!strokesQueue.isEmpty() && strokesQueue.last()->type() !=
KisStroke::LEGACY) {
582 std::pair<KisStrokeStrategy*, QList<KisStrokeJobData*>> fakeStrokePair
590 bool externalJobsPending)
592 updaterContext.
lock();
597 externalJobsPending));
605 return m_d->needsExclusiveAccess;
610 return m_d->wrapAroundModeSupported;
615 return m_d->balancingRatioOverride;
620 QMutexLocker locker(&
m_d->mutex);
631 QMutexLocker locker(&
m_d->mutex);
635 if (
m_d->lodPreferences.desiredLevelOfDetail() !=
m_d->nextDesiredLevelOfDetail ||
636 (
m_d->lodPreferences.lodPreferred() &&
m_d->lodNNeedsSynchronization)) {
638 m_d->nextDesiredLevelOfDetail =
m_d->lodPreferences.desiredLevelOfDetail();
639 m_d->switchDesiredLevelOfDetail(
false);
645 QMutexLocker locker(&
m_d->mutex);
646 return m_d->strokesQueue.isEmpty();
651 std::unique_lock<QMutex> locker(
m_d->mutex, std::try_to_lock);
652 if (!locker.owns_lock())
return false;
653 return m_d->strokesQueue.isEmpty();
658 QMutexLocker locker(&
m_d->mutex);
659 if(
m_d->strokesQueue.isEmpty())
return 0;
662 return qMax(1,
m_d->strokesQueue.head()->numJobs()) *
m_d->strokesQueue.size();
665void KisStrokesQueue::Private::switchDesiredLevelOfDetail(
bool forced)
667 if (forced || nextDesiredLevelOfDetail != desiredLevelOfDetail) {
673 const bool forgettable =
674 forced && !lodNNeedsSynchronization &&
675 desiredLevelOfDetail == nextDesiredLevelOfDetail;
677 desiredLevelOfDetail = nextDesiredLevelOfDetail;
678 lodNNeedsSynchronization |= !forgettable;
680 if (desiredLevelOfDetail && lodPreferences.lodPreferred()) {
681 startLod0ToNStroke(desiredLevelOfDetail, forgettable);
688 QMutexLocker locker(&
m_d->mutex);
689 m_d->switchDesiredLevelOfDetail(
true);
694 QMutexLocker locker(&
m_d->mutex);
696 m_d->forceResetLodAndCloseCurrentLodRange();
701 QMutexLocker locker(&
m_d->mutex);
705 qDebug() <<
ppVar(stroke->name()) <<
ppVar(stroke->type()) <<
ppVar(stroke->numJobs()) <<
ppVar(stroke->isInitialized()) <<
ppVar(stroke->isCancelled());
712 m_d->lod0ToNStrokeStrategyFactory = factory;
717 m_d->suspendResumeUpdatesStrokeStrategyFactory = factory;
722 m_d->purgeRedoStateCallback = callback;
727 m_d->postSyncLod0GUIPlaneRequestForResume = callback;
732 return &
m_d->lodNPostExecutionUndoAdapter;
737 QMutexLocker locker(&
m_d->mutex);
740 return m_d->strokesQueue.head()->name();
745 QMutexLocker locker(&
m_d->mutex);
746 return m_d->openedStrokesCounter;
750 bool externalJobsPending)
752 if(
m_d->strokesQueue.isEmpty())
return false;
759 const bool hasStrokeJobs = !(snapshot ==
ContextEmpty ||
775void KisStrokesQueue::Private::loadStroke(
KisStrokeSP stroke)
777 needsExclusiveAccess = stroke->isExclusive();
778 wrapAroundModeSupported = stroke->supportsWrapAroundMode();
779 balancingRatioOverride = stroke->balancingRatioOverride();
780 currentStrokeLoaded =
true;
788 if (purgeRedoStateCallback &&
789 stroke->clearsRedoOnStart()) {
791 purgeRedoStateCallback();
796 int runningLevelOfDetail)
806 bool hasJobs = stroke->hasJobs();
817 if(!stroke->isInitialized() && hasJobs && hasLodCompatibility) {
823 if (!
m_d->currentStrokeLoaded) {
824 m_d->loadStroke(stroke);
829 else if(hasJobs && hasLodCompatibility) {
834 if (!
m_d->currentStrokeLoaded) {
835 m_d->loadStroke(stroke);
840 else if(stroke->isEnded() && !hasJobs && !hasStrokeJobsRunning) {
841 m_d->tryClearUndoOnStrokeCompletion(stroke);
843 const bool needsSyncLod0PlaneToGUI =
845 stroke->isCancelled();
853 if (needsSyncLod0PlaneToGUI &&
854 m_d->postSyncLod0GUIPlaneRequestForResume) {
855 m_d->postSyncLod0GUIPlaneRequestForResume();
858 m_d->strokesQueue.dequeue();
859 m_d->needsExclusiveAccess =
false;
860 m_d->wrapAroundModeSupported =
false;
861 m_d->balancingRatioOverride = -1.0;
862 m_d->currentStrokeLoaded =
false;
864 m_d->switchDesiredLevelOfDetail(
false);
866 if(!
m_d->strokesQueue.isEmpty()) {
877 Q_UNUSED(hasStrokeJobs);
879 if(!
m_d->strokesQueue.head()->isExclusive())
return true;
880 return hasMergeJobs == 0;
884 bool externalJobsPending)
894 stroke->nextJobSequentiality();
913 externalJobsPending)) {
925 return runningLevelOfDetail < 0 ||
926 stroke->nextJobLevelOfDetail() == runningLevelOfDetail;
float value(const T *src, size_t ch)
@ HasUniquelyConcurrentJob
virtual KisStrokeJobData * createLodClone(int levelOfDetail)
bool forceLodModeIfPossible() const
void setMutatedJobsInterface(KisStrokesQueueMutatedJobInterface *mutatedJobsInterface, KisStrokeId strokeId)
bool canForgetAboutMe() const
virtual KisStrokeStrategy * createLodClone(int levelOfDetail)
bool isAsynchronouslyCancellable() const
void endStroke(KisStrokeId id) override
LodNUndoStrokesFacade(KisStrokesQueue *_q)
bool cancelStroke(KisStrokeId id) override
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override
void addJob(KisStrokeId id, KisStrokeJobData *data) override
KisUpdaterContextSnapshotEx getContextSnapshotEx() const
void addStrokeJob(KisStrokeJob *strokeJob)
int currentLevelOfDetail() const
#define KIS_SAFE_ASSERT_RECOVER(cond)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
#define KIS_ASSERT_RECOVER_RETURN(cond)
#define KIS_ASSERT_RECOVER_NOOP(cond)
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
typedef void(QOPENGLF_APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC)(GLuint buffer)
std::pair< KisStrokeStrategy *, QList< KisStrokeJobData * > > KisSuspendResumePair
std::function< std::pair< KisSuspendResumePair, KisSuspendResumePair >()> KisSuspendResumeStrategyPairFactory
std::pair< KisStrokeStrategy *, QList< KisStrokeJobData * > > KisLodSyncPair
std::function< KisLodSyncPair(bool)> KisLodSyncStrokeStrategyFactory
QQueue< KisStrokeSP >::iterator StrokesQueueIterator
QQueue< KisStrokeSP > StrokesQueue
StrokesQueue::iterator executeStrokePair(const StrokePair &pair, StrokesQueue &queue, typename StrokesQueue::iterator it, KisStroke::Type type, int levelOfDetail, KisStrokesQueueMutatedJobInterface *mutatedJobsInterface)
QSharedPointer< KisStroke > KisStrokeSP
UndoResult tryUndoLastStrokeAsync()
bool shouldWrapInSuspendUpdatesStroke()
void startLod0ToNStroke(int levelOfDetail, bool forgettable)
qint32 sizeMetric() const
qreal balancingRatioOverride
StrokesQueue strokesQueue
void forceResetLodAndCloseCurrentLodRange()
void switchDesiredLevelOfDetail(bool forced)
bool hasUnfinishedStrokes() const
void loadStroke(KisStrokeSP stroke)
void endStroke(KisStrokeId id)
bool checkExclusiveProperty(bool hasMergeJobs, bool hasStrokeJobs)
void cancelForgettableStrokes()
KisSurrogateUndoStore lodNUndoStore
int nextDesiredLevelOfDetail
Private(KisStrokesQueue *_q)
void addJob(KisStrokeId id, KisStrokeJobData *data)
void setPurgeRedoStateCallback(const std::function< void()> &callback)
bool tryCancelCurrentStrokeAsync()
void setLod0ToNStrokeStrategyFactory(const KisLodSyncStrokeStrategyFactory &factory)
void addMutatedJobs(KisStrokeId id, const QVector< KisStrokeJobData * > list) final override
bool checkStrokeState(bool hasStrokeJobsRunning, int runningLevelOfDetail)
bool processOneJob(KisUpdaterContext &updaterContext, bool externalJobsPending)
bool checkLevelOfDetailProperty(int runningLevelOfDetail)
bool needsExclusiveAccess
void setPostSyncLod0GUIPlaneRequestForResumeCallback(const std::function< void()> &callback)
StrokesQueueIterator findNewLod0Pos()
void debugDumpAllStrokes()
void setSuspendResumeUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyPairFactory &factory)
KisPostExecutionUndoAdapter lodNPostExecutionUndoAdapter
bool checkSequentialProperty(KisUpdaterContextSnapshotEx snapshot, bool externalJobsPending)
LodNUndoStrokesFacade lodNStrokesFacade
bool cancelStroke(KisStrokeId id)
void processQueue(KisUpdaterContext &updaterContext, bool externalJobsPending)
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy)
void tryClearUndoOnStrokeCompletion(KisStrokeSP finishingStroke)
bool hasOpenedStrokes() const
KUndo2MagicString currentStrokeName() const
StrokesQueueIterator findNewLodNPos(KisStrokeSP lodN)
void notifyUFOChangedImage()
std::function< void()> postSyncLod0GUIPlaneRequestForResume
bool wrapAroundModeSupported
bool lodNNeedsSynchronization
KisLodPreferences lodPreferences
KisSuspendResumeStrategyPairFactory suspendResumeUpdatesStrokeStrategyFactory
KisLodSyncStrokeStrategyFactory lod0ToNStrokeStrategyFactory
void explicitRegenerateLevelOfDetail()
std::pair< StrokesQueueIterator, StrokesQueueIterator > currentLodRange()
std::function< void()> purgeRedoStateCallback
KisStrokeId startLodNUndoStroke(KisStrokeStrategy *strokeStrategy)
void setLodPreferences(const KisLodPreferences &value)