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 QMutexLocker locker(&
m_d->mutex);
652 if(
m_d->strokesQueue.isEmpty())
return 0;
655 return qMax(1,
m_d->strokesQueue.head()->numJobs()) *
m_d->strokesQueue.size();
658void KisStrokesQueue::Private::switchDesiredLevelOfDetail(
bool forced)
660 if (forced || nextDesiredLevelOfDetail != desiredLevelOfDetail) {
666 const bool forgettable =
667 forced && !lodNNeedsSynchronization &&
668 desiredLevelOfDetail == nextDesiredLevelOfDetail;
670 desiredLevelOfDetail = nextDesiredLevelOfDetail;
671 lodNNeedsSynchronization |= !forgettable;
673 if (desiredLevelOfDetail && lodPreferences.lodPreferred()) {
674 startLod0ToNStroke(desiredLevelOfDetail, forgettable);
681 QMutexLocker locker(&
m_d->mutex);
682 m_d->switchDesiredLevelOfDetail(
true);
687 QMutexLocker locker(&
m_d->mutex);
689 m_d->forceResetLodAndCloseCurrentLodRange();
694 QMutexLocker locker(&
m_d->mutex);
698 qDebug() <<
ppVar(stroke->name()) <<
ppVar(stroke->type()) <<
ppVar(stroke->numJobs()) <<
ppVar(stroke->isInitialized()) <<
ppVar(stroke->isCancelled());
705 m_d->lod0ToNStrokeStrategyFactory = factory;
710 m_d->suspendResumeUpdatesStrokeStrategyFactory = factory;
715 m_d->purgeRedoStateCallback = callback;
720 m_d->postSyncLod0GUIPlaneRequestForResume = callback;
725 return &
m_d->lodNPostExecutionUndoAdapter;
730 QMutexLocker locker(&
m_d->mutex);
733 return m_d->strokesQueue.head()->name();
738 QMutexLocker locker(&
m_d->mutex);
739 return m_d->openedStrokesCounter;
743 bool externalJobsPending)
745 if(
m_d->strokesQueue.isEmpty())
return false;
752 const bool hasStrokeJobs = !(snapshot ==
ContextEmpty ||
768void KisStrokesQueue::Private::loadStroke(
KisStrokeSP stroke)
770 needsExclusiveAccess = stroke->isExclusive();
771 wrapAroundModeSupported = stroke->supportsWrapAroundMode();
772 balancingRatioOverride = stroke->balancingRatioOverride();
773 currentStrokeLoaded =
true;
781 if (purgeRedoStateCallback &&
782 stroke->clearsRedoOnStart()) {
784 purgeRedoStateCallback();
789 int runningLevelOfDetail)
799 bool hasJobs = stroke->hasJobs();
810 if(!stroke->isInitialized() && hasJobs && hasLodCompatibility) {
816 if (!
m_d->currentStrokeLoaded) {
817 m_d->loadStroke(stroke);
822 else if(hasJobs && hasLodCompatibility) {
827 if (!
m_d->currentStrokeLoaded) {
828 m_d->loadStroke(stroke);
833 else if(stroke->isEnded() && !hasJobs && !hasStrokeJobsRunning) {
834 m_d->tryClearUndoOnStrokeCompletion(stroke);
836 const bool needsSyncLod0PlaneToGUI =
838 stroke->isCancelled();
846 if (needsSyncLod0PlaneToGUI &&
847 m_d->postSyncLod0GUIPlaneRequestForResume) {
848 m_d->postSyncLod0GUIPlaneRequestForResume();
851 m_d->strokesQueue.dequeue();
852 m_d->needsExclusiveAccess =
false;
853 m_d->wrapAroundModeSupported =
false;
854 m_d->balancingRatioOverride = -1.0;
855 m_d->currentStrokeLoaded =
false;
857 m_d->switchDesiredLevelOfDetail(
false);
859 if(!
m_d->strokesQueue.isEmpty()) {
870 Q_UNUSED(hasStrokeJobs);
872 if(!
m_d->strokesQueue.head()->isExclusive())
return true;
873 return hasMergeJobs == 0;
877 bool externalJobsPending)
887 stroke->nextJobSequentiality();
906 externalJobsPending)) {
918 return runningLevelOfDetail < 0 ||
919 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)