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