1 /******************************************************************************
2  *
3  *  SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *
7  *******************************************************************************/
8 
9 #include "core/modelinvariantrowmapper.h"
10 #include "core/modelinvariantindex_p.h"
11 #include "core/modelinvariantrowmapper_p.h"
12 
13 #include <QTime>
14 #include <QTimer>
15 
16 #include "messagelist_debug.h"
17 
18 namespace MessageList
19 {
20 namespace Core
21 {
22 class RowShift
23 {
24 public:
25     int mMinimumRowIndex;
26     int mShift;
27     QHash<int, ModelInvariantIndex *> *mInvariantHash;
28 
29 public:
RowShift(int minRowIndex,int shift,QHash<int,ModelInvariantIndex * > * invariantHash)30     RowShift(int minRowIndex, int shift, QHash<int, ModelInvariantIndex *> *invariantHash)
31         : mMinimumRowIndex(minRowIndex)
32         , mShift(shift)
33         , mInvariantHash(invariantHash)
34     {
35     }
36 
~RowShift()37     ~RowShift()
38     {
39         for (const auto idx : std::as_const(*mInvariantHash)) {
40             idx->d->setRowMapper(nullptr);
41         }
42         delete mInvariantHash;
43     }
44 };
45 } // namespace Core
46 } // namespace MessageList
47 
48 using namespace MessageList::Core;
49 
ModelInvariantRowMapper()50 ModelInvariantRowMapper::ModelInvariantRowMapper()
51     : d(new ModelInvariantRowMapperPrivate(this))
52 {
53     d->mRowShiftList = new QList<RowShift *>();
54     d->mCurrentShiftSerial = 0;
55     d->mCurrentInvariantHash = new QHash<int, ModelInvariantIndex *>();
56     d->mUpdateTimer = new QTimer(this);
57     d->mUpdateTimer->setSingleShot(true);
58     d->mLazyUpdateChunkInterval = 50;
59     d->mLazyUpdateIdleInterval = 50;
60 
61     connect(d->mUpdateTimer, &QTimer::timeout, this, [this]() {
62         d->slotPerformLazyUpdate();
63     });
64 }
65 
~ModelInvariantRowMapper()66 ModelInvariantRowMapper::~ModelInvariantRowMapper()
67 {
68     if (d->mUpdateTimer->isActive()) {
69         d->mUpdateTimer->stop();
70     }
71 
72     // FIXME: optimize this (it CAN be optimized)
73     for (const auto idx : std::as_const(*d->mCurrentInvariantHash)) {
74         idx->d->setRowMapper(nullptr);
75     }
76     delete d->mCurrentInvariantHash;
77 
78     if (d->mRowShiftList) {
79         while (!d->mRowShiftList->isEmpty()) {
80             delete d->mRowShiftList->takeFirst();
81         }
82 
83         delete d->mRowShiftList;
84     }
85 }
86 
killFirstRowShift()87 void ModelInvariantRowMapperPrivate::killFirstRowShift()
88 {
89     RowShift *shift = mRowShiftList->at(0);
90 
91     Q_ASSERT(shift->mInvariantHash->isEmpty());
92 
93     delete shift;
94     mRowShiftList->removeAt(0);
95     mRemovedShiftCount++;
96     if (mRowShiftList->isEmpty()) {
97         delete mRowShiftList;
98         mRowShiftList = nullptr;
99     }
100 }
101 
indexDead(ModelInvariantIndex * invariant)102 void ModelInvariantRowMapperPrivate::indexDead(ModelInvariantIndex *invariant)
103 {
104     Q_ASSERT(invariant->d->rowMapper() == q);
105 
106     if (invariant->d->rowMapperSerial() == mCurrentShiftSerial) {
107         mCurrentInvariantHash->remove(invariant->d->modelIndexRow());
108         return;
109     }
110 
111     Q_ASSERT(invariant->d->rowMapperSerial() < mCurrentShiftSerial);
112 
113     if (!mRowShiftList) {
114         return; // not found (not requested yet or invalid index at all)
115     }
116 
117     uint invariantShiftIndex = invariant->d->rowMapperSerial() - mRemovedShiftCount;
118 
119     Q_ASSERT(invariantShiftIndex < static_cast<uint>(mRowShiftList->count()));
120 
121     RowShift *shift = mRowShiftList->at(invariantShiftIndex);
122 
123     Q_ASSERT(shift);
124 
125     shift->mInvariantHash->remove(invariant->d->modelIndexRow());
126 
127     if ((shift->mInvariantHash->isEmpty()) && (invariantShiftIndex == 0)) {
128         // no more invariants with serial <= invariant->d->rowMapperSerial()
129         killFirstRowShift();
130     }
131 }
132 
updateModelInvariantIndex(int modelIndexRow,ModelInvariantIndex * invariantToFill)133 void ModelInvariantRowMapperPrivate::updateModelInvariantIndex(int modelIndexRow, ModelInvariantIndex *invariantToFill)
134 {
135     // Here the invariant already belongs to this mapper. We ASSUME that it's somewhere
136     // in the history and not in the hash belonging to the current serial.
137     // modelIndexRow is the CURRENT model index row.
138     Q_ASSERT(invariantToFill->d->rowMapper() == q);
139 
140     uint invariantShiftIndex = invariantToFill->d->rowMapperSerial() - mRemovedShiftCount;
141 
142     Q_ASSERT(invariantShiftIndex < static_cast<uint>(mRowShiftList->count()));
143 
144     RowShift *shift = mRowShiftList->at(invariantShiftIndex);
145 
146     int count = shift->mInvariantHash->remove(invariantToFill->d->modelIndexRow());
147 
148     Q_ASSERT(count > 0);
149     Q_UNUSED(count)
150 
151     // update and make it belong to the current serial
152     invariantToFill->d->setModelIndexRowAndRowMapperSerial(modelIndexRow, mCurrentShiftSerial);
153 
154     Q_ASSERT(!mCurrentInvariantHash->contains(invariantToFill->d->modelIndexRow()));
155 
156     mCurrentInvariantHash->insert(invariantToFill->d->modelIndexRow(), invariantToFill);
157 
158     if ((shift->mInvariantHash->isEmpty()) && (invariantShiftIndex == 0)) {
159         // no more invariants with serial <= invariantToFill->rowMapperSerial()
160         killFirstRowShift();
161     }
162 }
163 
modelIndexRowToModelInvariantIndexInternal(int modelIndexRow,bool updateIfNeeded)164 ModelInvariantIndex *ModelInvariantRowMapperPrivate::modelIndexRowToModelInvariantIndexInternal(int modelIndexRow, bool updateIfNeeded)
165 {
166     // First of all look it up in the current hash
167     ModelInvariantIndex *invariant = mCurrentInvariantHash->value(modelIndexRow, nullptr);
168     if (invariant) {
169         return invariant; // found: was up to date
170     }
171 
172     // Go backward in history by unapplying changes
173     if (!mRowShiftList) {
174         return nullptr; // not found (not requested yet or invalid index at all)
175     }
176 
177     int idx = mRowShiftList->count();
178     if (idx == 0) {
179         Q_ASSERT(false);
180         return nullptr; // should never happen (mRowShiftList should have been 0), but well...
181     }
182     idx--;
183 
184     int previousIndexRow = modelIndexRow;
185 
186     while (idx >= 0) {
187         RowShift *shift = mRowShiftList->at(idx);
188 
189         // this shift has taken "previousModelIndexRow" in the historic state
190         // and has executed:
191         //
192         //   if ( previousIndexRow >= shift->mMinimumRowIndex )
193         //     previousIndexRow += shift->mShift;
194         //
195         // so inverting it
196         //
197         //   int potentialPreviousModelIndexRow = modelIndexRow - shift->mShift;
198         //   if ( potentialPreviousModelIndexRow >= shift->mMinimumRowIndex )
199         //     previousIndexRow = potentialPreviousModelIndexRow;
200         //
201         // or by simplifying...
202 
203         int potentialPreviousModelIndexRow = previousIndexRow - shift->mShift;
204         if (potentialPreviousModelIndexRow >= shift->mMinimumRowIndex) {
205             previousIndexRow = potentialPreviousModelIndexRow;
206         }
207 
208         invariant = shift->mInvariantHash->value(previousIndexRow, nullptr);
209         if (invariant) {
210             // found at this level in history
211             if (updateIfNeeded) { // update it too
212                 updateModelInvariantIndex(modelIndexRow, invariant);
213             }
214             return invariant;
215         }
216 
217         idx--;
218     }
219 
220     qCWarning(MESSAGELIST_LOG) << "Requested invariant for storage row index " << modelIndexRow << " not found in history";
221     return nullptr; // not found in history
222 }
223 
setLazyUpdateChunkInterval(int chunkInterval)224 void ModelInvariantRowMapper::setLazyUpdateChunkInterval(int chunkInterval)
225 {
226     d->mLazyUpdateChunkInterval = chunkInterval;
227 }
228 
setLazyUpdateIdleInterval(int idleInterval)229 void ModelInvariantRowMapper::setLazyUpdateIdleInterval(int idleInterval)
230 {
231     d->mLazyUpdateIdleInterval = idleInterval;
232 }
233 
modelInvariantIndexToModelIndexRow(ModelInvariantIndex * invariant)234 int ModelInvariantRowMapper::modelInvariantIndexToModelIndexRow(ModelInvariantIndex *invariant)
235 {
236     // the invariant shift serial is the serial this mapper
237     // had at the time it emitted the invariant.
238     // mRowShiftList at that time had at most invariantShiftSerial items.
239     Q_ASSERT(invariant);
240 
241     if (invariant->d->rowMapper() != this) {
242         return -1;
243     }
244 
245     if (invariant->d->rowMapperSerial() == d->mCurrentShiftSerial) {
246         Q_ASSERT(d->mCurrentInvariantHash->value(invariant->d->modelIndexRow()) == invariant);
247         return invariant->d->modelIndexRow(); // this invariant was emitted very recently and isn't affected by any change
248     }
249 
250     // If RowShift elements weren't removed from the list then
251     // we should have mCurrentShiftSerial items in the list.
252     // But RowShifts ARE removed sequentially from the beginning of the list
253     // as the invariants are updated in the user's data.
254     // We are making sure that if a RowShift belonging to a certain
255     // serial is removed from the list then there are no more
256     // ModelInvariantIndexinstances with that (or a lower) serial around.
257     // Thus invariantShiftSerial is >= mRemovedShiftCount.
258 
259     // Example:
260     //     Initial state, no shifts, current serial 0, removed shifts 0
261     //     Emit ModelInvariantIndexfor model index row 6, with serial 0.
262     //     User asks for model index row of invariant that has row index 10 and serial 0.
263     //       The serial is equal to the current serial and we return the row index unchanged.
264     //     A row arrives at position 4
265     //       We add a RowShift with start index 5 and offset +1
266     //       We increase current serial to 1
267     //     User asks for model index row of invariant that has row index 6 with serial 0.
268     //       We compute the first RowShift index as serial 0 - removed 0 = 0
269     //       We apply the row shifts starting at that index.
270     //         That is, since the requested row index is 6 >= 5
271     //           We apply +1 shift and return row index 7 serial 1
272     //     User asks for model index row of invariant that has row index 7 with serial 1
273     //       The serial is equal to the current serial and we return the row index unchanged still with serial 1
274     //     We update all the invariants in the user's data so that
275     //     there are no more invariants with serial 0.
276     //       We remove the RowShift and increase removed shift count to 1
277     //     User asks for model index row of invariant that has row index 7
278     //       The ModelInvariantIndex MUST have at least serial 1 because of the removal step above.
279     //       The serial is equal to the current serial and we return the row index unchanged still with serial 1
280     //     A row arrives at position 2
281     //       We add a RowShift with start index 3 and offset +1
282     //       We increase current serial to 2
283     //     User asks for model index row of invariant that has row index 7 with serial 1.
284     //       We compute the first RowShift index as serial 1 - removed 1 = 0
285     //       We apply the row shifts starting at that index.
286     //         That is, since the requested row index is 7 >= 3
287     //           We apply +1 shift and return row index 8 serial 2
288     //     User asks for model index row of invariant that has row index 8 and serial 2
289     //       The serial is equal to the current serial and we return the row index unchanged still with serial 2
290     //     Etc...
291 
292     // So if we can trust that the user doesn't mess up with serials
293     // and the requested serial is not equal to the current serial
294     // then we can be 100% sure that mRowShiftList is not null (it contains at least one item).
295     // The requested serial is surely >= than mRemovedShiftCount too.
296 
297     // To find the starting index of the RowShifts that apply to this
298     // serial we need to offset them by the removed rows.
299 
300     uint invariantShiftIndex = invariant->d->rowMapperSerial() - d->mRemovedShiftCount;
301 
302     Q_ASSERT(d->mRowShiftList);
303 
304     // For the reasoning above invariantShiftIndex is surely < than mRowShiftList.count()
305 
306     const uint count = static_cast<uint>(d->mRowShiftList->count());
307 
308     Q_ASSERT(invariantShiftIndex < count);
309 
310     int modelIndexRow = invariant->d->modelIndexRow();
311 
312     // apply shifts
313     for (uint idx = invariantShiftIndex; idx < count; idx++) {
314         RowShift *shift = d->mRowShiftList->at(idx);
315         if (modelIndexRow >= shift->mMinimumRowIndex) {
316             modelIndexRow += shift->mShift;
317         }
318     }
319 
320     // Update the invariant on-the-fly too...
321     d->updateModelInvariantIndex(modelIndexRow, invariant);
322 
323     return modelIndexRow;
324 }
325 
createModelInvariantIndex(int modelIndexRow,ModelInvariantIndex * invariantToFill)326 void ModelInvariantRowMapper::createModelInvariantIndex(int modelIndexRow, ModelInvariantIndex *invariantToFill)
327 {
328     // The user is athemeg for the invariant of the item that is at the CURRENT modelIndexRow.
329     Q_ASSERT(invariantToFill->d->rowMapper() == nullptr);
330 
331     // Plain new invariant. Fill it and add to the current hash.
332     invariantToFill->d->setModelIndexRowAndRowMapperSerial(modelIndexRow, d->mCurrentShiftSerial);
333     invariantToFill->d->setRowMapper(this);
334 
335     Q_ASSERT(!d->mCurrentInvariantHash->contains(modelIndexRow));
336 
337     d->mCurrentInvariantHash->insert(modelIndexRow, invariantToFill);
338 }
339 
modelIndexRowToModelInvariantIndex(int modelIndexRow)340 ModelInvariantIndex *ModelInvariantRowMapper::modelIndexRowToModelInvariantIndex(int modelIndexRow)
341 {
342     return d->modelIndexRowToModelInvariantIndexInternal(modelIndexRow, false);
343 }
344 
modelIndexRowRangeToModelInvariantIndexList(int startIndexRow,int count)345 QList<ModelInvariantIndex *> *ModelInvariantRowMapper::modelIndexRowRangeToModelInvariantIndexList(int startIndexRow, int count)
346 {
347     if (!d->mRowShiftList) {
348         if (d->mCurrentInvariantHash->isEmpty()) {
349             return nullptr; // no invariants emitted, even if rows are changed, no invariant is affected.
350         }
351     }
352 
353     // Find the invariants in range.
354     // It's somewhat impossible to split this in chunks.
355 
356     auto invariantList = new QList<ModelInvariantIndex *>();
357 
358     const int end = startIndexRow + count;
359     for (int idx = startIndexRow; idx < end; idx++) {
360         ModelInvariantIndex *invariant = d->modelIndexRowToModelInvariantIndexInternal(idx, true);
361         if (invariant) {
362             invariantList->append(invariant);
363         }
364     }
365 
366     if (invariantList->isEmpty()) {
367         delete invariantList;
368         return nullptr;
369     }
370 
371     return invariantList;
372 }
373 
modelRowsInserted(int modelIndexRowPosition,int count)374 void ModelInvariantRowMapper::modelRowsInserted(int modelIndexRowPosition, int count)
375 {
376     // Some rows were added to the model at modelIndexRowPosition.
377 
378     // FIXME: If rows are added at the end then we don't need any mapping.
379     //        The fact is that we don't know which is the model's end...
380     //        But maybe we can consider the end being the greatest row
381     //        index emitted until now...
382 
383     if (!d->mRowShiftList) {
384         if (d->mCurrentInvariantHash->isEmpty()) {
385             return; // no invariants emitted, even if rows are changed, no invariant is affected.
386         }
387         // some invariants might be affected
388         d->mRowShiftList = new QList<RowShift *>();
389     }
390 
391     RowShift *shift;
392 
393     if (d->mCurrentInvariantHash->isEmpty()) {
394         // No invariants updated (all existing are outdated)
395 
396         Q_ASSERT(d->mRowShiftList->count() > 0); // must be true since it's not null
397 
398         // Check if we can attach to the last existing shift (very common for consecutive row additions)
399         shift = d->mRowShiftList->at(d->mRowShiftList->count() - 1);
400         Q_ASSERT(shift);
401 
402         if (shift->mShift > 0) { // the shift was positive (addition)
403             if ((shift->mMinimumRowIndex + shift->mShift) == modelIndexRowPosition) {
404                 // Inserting contiguous blocks of rows, just extend this shift
405                 shift->mShift += count;
406                 Q_ASSERT(d->mUpdateTimer->isActive());
407                 return;
408             }
409         }
410     }
411 
412     // FIXME: If we have few items, we can just shift the indexes now.
413 
414     shift = new RowShift(modelIndexRowPosition, count, d->mCurrentInvariantHash);
415     d->mRowShiftList->append(shift);
416 
417     d->mCurrentShiftSerial++;
418     d->mCurrentInvariantHash = new QHash<int, ModelInvariantIndex *>();
419 
420     if (d->mRowShiftList->count() > 7) { // 7 is heuristic
421         // We start losing performance as the stack is growing too much.
422         // Start updating NOW and hope we can get it in few sweeps.
423 
424         if (d->mUpdateTimer->isActive()) {
425             d->mUpdateTimer->stop();
426         }
427 
428         d->slotPerformLazyUpdate();
429     } else {
430         // Make sure we'll get a lazy update somewhere in the future
431         if (!d->mUpdateTimer->isActive()) {
432             d->mUpdateTimer->start(d->mLazyUpdateIdleInterval);
433         }
434     }
435 }
436 
modelRowsRemoved(int modelIndexRowPosition,int count)437 QList<ModelInvariantIndex *> *ModelInvariantRowMapper::modelRowsRemoved(int modelIndexRowPosition, int count)
438 {
439     // Some rows were added from the model at modelIndexRowPosition.
440 
441     // FIXME: If rows are removed from the end, we don't need any mapping.
442     //        The fact is that we don't know which is the model's end...
443     //        But maybe we can consider the end being the greatest row
444     //        index emitted until now...
445 
446     if (!d->mRowShiftList) {
447         if (d->mCurrentInvariantHash->isEmpty()) {
448             return nullptr; // no invariants emitted, even if rows are changed, no invariant is affected.
449         }
450         // some invariants might be affected
451     }
452 
453     // FIXME: If we have few items, we can just shift the indexes now.
454 
455     // FIXME: Find a way to "merge" the shifts, if possible
456     //        It OFTEN happens that we remove a lot of items at once (as opposed
457     //        to item addition which is usually an incremental operation).
458 
459     // FIXME: HUGE PROBLEM
460     //        When the items aren't contiguous or are just out of order it's
461     //        impossible to merge the shifts. Deleting many messages
462     //        generates then a very deep delta stack. Since to delete the
463     //        next message you need to traverse the whole stack, this method
464     //        becomes very slow (maybe not as slow as updating all the indexes
465     //        in the general case, but still *slow*).
466     //
467     //        So one needs to perform updates while rows are being removed
468     //        but that tends to void all your efforts to not update the
469     //        whole list of items every time...
470     //
471     //        Also deletions don't seem to be asynchronous (or at least
472     //        they eat all the CPU power available for KMail) so the timers
473     //        don't fire and we're not actually processing the model jobs...
474     //
475     //        It turns out that deleting many items is just slower than
476     //        reloading the view...
477 
478     // Invalidate the invariants affected by the change
479     // In most cases it's a relatively small sweep (and it's done once).
480     // It's somewhat impossible to split this in chunks.
481 
482     auto deadInvariants = new QList<ModelInvariantIndex *>();
483 
484     const int end = modelIndexRowPosition + count;
485     for (int idx = modelIndexRowPosition; idx < end; idx++) {
486         // FIXME: One could optimize this by joining the retrieval and destruction functions
487         //        that is by making a special indexDead( int modelIndex )..
488         ModelInvariantIndex *dyingInvariant = d->modelIndexRowToModelInvariantIndexInternal(idx, false);
489         if (dyingInvariant) {
490             d->indexDead(dyingInvariant); // will remove from this mapper hashes
491             dyingInvariant->d->setRowMapper(nullptr); // invalidate!
492             deadInvariants->append(dyingInvariant);
493         } else {
494             // got no dying invariant
495             qCWarning(MESSAGELIST_LOG) << "Could not find invariant to invalidate at current row " << idx;
496         }
497     }
498 
499     if (!d->mRowShiftList) {
500         // have no pending shifts, look if we are keeping other invariants
501         if (d->mCurrentInvariantHash->isEmpty()) {
502             // no more invariants in this mapper, even if rows are changed, no invariant is affected.
503             if (deadInvariants->isEmpty()) {
504                 // should never happen, but well...
505                 delete deadInvariants;
506                 return nullptr;
507             }
508             return deadInvariants;
509         }
510         // still have some invariants inside, must add a shift for them
511         d->mRowShiftList = new QList<RowShift *>();
512     } // else already have shifts
513 
514     // add a shift for this row removal
515     auto shift = new RowShift(modelIndexRowPosition + count, -count, d->mCurrentInvariantHash);
516     d->mRowShiftList->append(shift);
517 
518     d->mCurrentShiftSerial++;
519     d->mCurrentInvariantHash = new QHash<int, ModelInvariantIndex *>();
520 
521     // trigger updates
522     if (d->mRowShiftList->count() > 7) { // 7 is heuristic
523         // We start losing performance as the stack is growing too much.
524         // Start updating NOW and hope we can get it in few sweeps.
525 
526         if (d->mUpdateTimer->isActive()) {
527             d->mUpdateTimer->stop();
528         }
529 
530         d->slotPerformLazyUpdate();
531     } else {
532         // Make sure we'll get a lazy update somewhere in the future
533         if (!d->mUpdateTimer->isActive()) {
534             d->mUpdateTimer->start(d->mLazyUpdateIdleInterval);
535         }
536     }
537 
538     if (deadInvariants->isEmpty()) {
539         // should never happen, but well...
540         delete deadInvariants;
541         return nullptr;
542     }
543 
544     return deadInvariants;
545 }
546 
modelReset()547 void ModelInvariantRowMapper::modelReset()
548 {
549     // FIXME: optimize this (it probably can be optimized by providing a more complex user interface)
550 
551     for (const auto idx : std::as_const(*d->mCurrentInvariantHash)) {
552         idx->d->setRowMapper(nullptr);
553     }
554     d->mCurrentInvariantHash->clear();
555 
556     if (d->mRowShiftList) {
557         while (!d->mRowShiftList->isEmpty()) {
558             delete d->mRowShiftList->takeFirst();
559         }
560 
561         delete d->mRowShiftList;
562         d->mRowShiftList = nullptr;
563     }
564 
565     d->mCurrentShiftSerial = 0;
566     d->mRemovedShiftCount = 0;
567 }
568 
slotPerformLazyUpdate()569 void ModelInvariantRowMapperPrivate::slotPerformLazyUpdate()
570 {
571     // The drawback here is that when one row is removed from the middle (say position 500 of 1000)
572     // then we require ALL the items to be updated...but:
573     //
574     // - We can do it very lazily in the background
575     // - Optimizing this would mean to ALSO keep the indexes in lists or in a large array
576     //   - The list approach would require to keep the indexes sorted
577     //     so it would cost at least N log (N) / 2.. which is worse than N.
578     //   - We could keep a single (or multiple) array as large as the model
579     //     but then we'd have a large memory consumption and large overhead
580     //     when inserting / removing items from the middle.
581     //
582     // So finally I think that the multiple hash approach is a "minimum loss" approach.
583 
584     QTime startTime = QTime::currentTime();
585 
586     int curIndex = 0;
587 
588     while (mRowShiftList) {
589         // Have at least one row shift
590         uint count = static_cast<uint>(mRowShiftList->count());
591 
592         // Grab it
593         RowShift *shift = mRowShiftList->at(0);
594 
595         // and update the invariants that belong to it
596         auto it = shift->mInvariantHash->begin();
597         auto end = shift->mInvariantHash->end();
598 
599         while (it != end) {
600             ModelInvariantIndex *invariant = *it;
601 
602             it = shift->mInvariantHash->erase(it);
603 
604             // apply shifts
605             int modelIndexRow = invariant->d->modelIndexRow();
606 
607             for (uint idx = 0; idx < count; ++idx) {
608                 RowShift *thatShift = mRowShiftList->at(idx);
609                 if (modelIndexRow >= thatShift->mMinimumRowIndex) {
610                     modelIndexRow += thatShift->mShift;
611                 }
612             }
613 
614             // update and make it belong to the current serial
615             invariant->d->setModelIndexRowAndRowMapperSerial(modelIndexRow, mCurrentShiftSerial);
616 
617             mCurrentInvariantHash->insert(modelIndexRow, invariant);
618 
619             // once in a while check if we ran out of time
620             if ((curIndex % 15) == 0) { // 15 is heuristic
621                 int elapsed = startTime.msecsTo(QTime::currentTime());
622                 if ((elapsed > mLazyUpdateChunkInterval) || (elapsed < 0)) {
623                     // interrupt
624                     // qCDebug(MESSAGELIST_LOG) << "Lazy update fixed " << curIndex << " invariants ";
625                     mUpdateTimer->start(mLazyUpdateIdleInterval);
626                     return;
627                 }
628             }
629 
630             curIndex++;
631         }
632 
633         // no more invariants with serial <= invariantToFill->rowMapperSerial()
634         killFirstRowShift();
635     }
636 
637     // qCDebug(MESSAGELIST_LOG) << "Lazy update fixed " << curIndex << " invariants ";
638 
639     // if we're here then no more work needs to be done.
640 }
641 
642 #include "moc_modelinvariantrowmapper.cpp"
643