Krita Source Code Documentation
Loading...
Searching...
No Matches
KisMementoManager Class Reference

#include <kis_memento_manager.h>

Public Member Functions

void commit ()
 
KisMementoSP currentMemento ()
 
void debugPrintInfo ()
 
KisTileSP getCommittedTile (qint32 col, qint32 row, bool &existingTile)
 
KisMementoSP getMemento ()
 
bool hasCurrentMemento ()
 
 KisMementoManager ()
 
 KisMementoManager (const KisMementoManager &rhs)
 
void purgeHistory (KisMementoSP oldestMemento)
 
void registerTileChange (KisTile *tile)
 
void registerTileDeleted (KisTile *tile)
 
void rollback (KisTileHashTable *ht, KisMementoSP memento)
 
void rollforward (KisTileHashTable *ht, KisMementoSP memento)
 
void setDefaultTileData (KisTileData *defaultTileData)
 
 ~KisMementoManager ()
 

Protected Member Functions

qint32 findRevisionByMemento (KisMementoSP memento) const
 
void resetRevisionHistory (KisMementoItemList list)
 

Protected Attributes

KisHistoryList m_cancelledRevisions
 
KisMementoSP m_currentMemento
 
QMutex m_currentMementoExtentLock
 
KisMementoItemHashTable m_headsHashTable
 
KisMementoItemHashTable m_index
 
bool m_registrationBlocked
 
KisHistoryList m_revisions
 

Detailed Description

Definition at line 45 of file kis_memento_manager.h.

Constructor & Destructor Documentation

◆ KisMementoManager() [1/2]

KisMementoManager::KisMementoManager ( )

Tile change/delete registration is enabled for all devices by default. It can't be delayed.

Definition at line 64 of file kis_memento_manager.cc.

65 : m_index(0),
68{
73}
KisMementoItemHashTable m_index
KisMementoItemHashTable m_headsHashTable

◆ KisMementoManager() [2/2]

KisMementoManager::KisMementoManager ( const KisMementoManager & rhs)

Definition at line 75 of file kis_memento_manager.cc.

76 : m_index(rhs.m_index, 0),
82{
83 Q_ASSERT_X(!m_registrationBlocked,
84 "KisMementoManager", "(impossible happened) "
85 "The device has been copied while registration was blocked");
86}
KisMementoSP m_currentMemento
KisHistoryList m_cancelledRevisions
KisHistoryList m_revisions

References m_registrationBlocked.

◆ ~KisMementoManager()

KisMementoManager::~KisMementoManager ( )

Definition at line 88 of file kis_memento_manager.cc.

89{
90 // Nothing to be done here. Happily...
91 // Everything is done by QList and KisSharedPtr...
93}
#define DEBUG_LOG_SIMPLE_ACTION(action)

References DEBUG_LOG_SIMPLE_ACTION.

Member Function Documentation

◆ commit()

void KisMementoManager::commit ( )

Commits changes, made in INDEX: appends m_index into m_revisions list and owes all modified tileDatas.

We still need to continue commit, because a named transaction may be reverted by the user

Definition at line 166 of file kis_memento_manager.cc.

167{
168 if (m_index.isEmpty()) {
170 //warnTiles << "Named Transaction is empty";
175 }
176 else {
178 return;
179 }
180 }
181
182 KisMementoItemList revisionList;
184 KisMementoItemSP parentMI;
185 bool newTile;
186
188 while ((mi = iter.tile())) {
189 parentMI = m_headsHashTable.getTileLazy(mi->col(), mi->row(), newTile);
190
191 mi->setParent(parentMI);
192 mi->commit();
193 revisionList.append(mi);
194
195 m_headsHashTable.deleteTile(mi->col(), mi->row());
196
197 iter.moveCurrentToHashTable(&m_headsHashTable);
198 //iter.next(); // previous line does this for us
199 }
200
201 KisHistoryItem hItem;
202 hItem.itemList = revisionList;
203 hItem.memento = m_currentMemento.data();
204 m_revisions.append(hItem);
205
208
209 DEBUG_DUMP_MESSAGE("COMMIT_DONE");
210
211 // Waking up pooler to prepare copies for us
213}
qint32 col() const
qint32 row() const
void setParent(KisMementoItemSP parent)
static KisTileDataStore * instance()
bool deleteTile(TileTypeSP tile)
TileTypeSP getTileLazy(qint32 col, qint32 row, bool &newTile)
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#define DEBUG_DUMP_MESSAGE(action)
#define namedTransactionInProgress()
struct Tile * newTile(struct rect r)
Definition pixels.c:231
KisMementoItemList itemList

References KisMementoItem::col(), KisMementoItem::commit(), KisSharedPtr< T >::data(), DEBUG_DUMP_MESSAGE, KisTileHashTableTraits< T >::deleteTile(), KisTileHashTableTraits< T >::getTileLazy(), KisTileDataStore::instance(), KisTileHashTableTraits< T >::isEmpty(), KisHistoryItem::itemList, KisTileDataStore::kickPooler(), KIS_ASSERT, m_currentMemento, m_headsHashTable, m_index, m_revisions, KisHistoryItem::memento, KisTileHashTableIteratorTraits< T, LockerType >::moveCurrentToHashTable(), namedTransactionInProgress, newTile(), KisMementoItem::row(), KisMementoItem::setParent(), and KisTileHashTableIteratorTraits< T, LockerType >::tile().

◆ currentMemento()

KisMementoSP KisMementoManager::currentMemento ( )

Definition at line 257 of file kis_memento_manager.cc.

257 {
258 return m_currentMemento;
259}

References m_currentMemento.

◆ debugPrintInfo()

void KisMementoManager::debugPrintInfo ( )

Definition at line 407 of file kis_memento_manager.cc.

408{
409 printf("KisMementoManager stats:\n");
410 printf("Index list\n");
413
414 while ((mi = iter.tile())) {
415 mi->debugPrintInfo();
416 iter.next();
417 }
418
419 printf("Revisions list:\n");
420 qint32 i = 0;
421 Q_FOREACH (const KisHistoryItem &changeList, m_revisions) {
422 printf("--- revision #%d ---\n", i++);
423 Q_FOREACH (mi, changeList.itemList) {
424 mi->debugPrintInfo();
425 }
426 }
427
428 printf("\nCancelled revisions list:\n");
429 i = 0;
430 Q_FOREACH (const KisHistoryItem &changeList, m_cancelledRevisions) {
431 printf("--- revision #%d ---\n", m_revisions.size() + i++);
432 Q_FOREACH (mi, changeList.itemList) {
433 mi->debugPrintInfo();
434 }
435 }
436
437 printf("----------------\n");
439}

References KisMementoItem::debugPrintInfo(), KisTileHashTableTraits< T >::debugPrintInfo(), KisHistoryItem::itemList, m_cancelledRevisions, m_headsHashTable, m_index, m_revisions, KisTileHashTableIteratorTraits< T, LockerType >::next(), and KisTileHashTableIteratorTraits< T, LockerType >::tile().

◆ findRevisionByMemento()

qint32 KisMementoManager::findRevisionByMemento ( KisMementoSP memento) const
protected

Definition at line 373 of file kis_memento_manager.cc.

374{
375 qint32 index = -1;
376 for(qint32 i = 0; i < m_revisions.size(); i++) {
377 if (m_revisions[i].memento == memento) {
378 index = i;
379 break;
380 }
381 }
382 return index;
383}

References m_revisions.

◆ getCommittedTile()

KisTileSP KisMementoManager::getCommittedTile ( qint32 col,
qint32 row,
bool & existingTile )

Get old tile, whose memento is in the HEAD revision. existingTile returns if the tile is actually an existing non-default tile or it was created on the fly from the default tile data

Our getOldTile mechanism is supposed to return current tile, if the history is disabled. So we return zero if no named transaction is in progress.

Definition at line 215 of file kis_memento_manager.cc.

216{
223 return KisTileSP();
224
225 KisMementoItemSP mi = m_headsHashTable.getReadOnlyTileLazy(col, row, existingTile);
226 return mi->tile(0);
227}
KisTileSP tile(KisMementoManager *mm)
TileTypeSP getReadOnlyTileLazy(qint32 col, qint32 row, bool &existingTile)
KisSharedPtr< KisTile > KisTileSP
Definition kis_tile.h:27

References KisTileHashTableTraits< T >::getReadOnlyTileLazy(), m_headsHashTable, namedTransactionInProgress, and KisMementoItem::tile().

◆ getMemento()

KisMementoSP KisMementoManager::getMemento ( )

We do not allow nested transactions

The following assert is useful for testing if some code creates a transaction on a device with "inconsistent history". We cannot keep this sanity check enabled all the time, because in some places (e.g. projection in KisAsyncMerger) such usecase is considered legit. But in places with "consistent history", e.g. in layer's paint device, such usage will cause undo corruption.

Definition at line 229 of file kis_memento_manager.cc.

230{
235
244 // KIS_SAFE_ASSERT_RECOVER_NOOP(m_index.isEmpty());
245
246 // Clear redo() information
247 m_cancelledRevisions.clear();
248
249 commit();
250 m_currentMemento = new KisMemento(this);
251
252 DEBUG_LOG_SIMPLE_ACTION("GET_MEMENTO_DONE");
253
254 return m_currentMemento;
255}
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130

References commit(), DEBUG_LOG_SIMPLE_ACTION, KIS_SAFE_ASSERT_RECOVER_NOOP, m_cancelledRevisions, m_currentMemento, and namedTransactionInProgress.

◆ hasCurrentMemento()

bool KisMementoManager::hasCurrentMemento ( )
inline

Definition at line 98 of file kis_memento_manager.h.

98 {
99 return m_currentMemento;
100 }

◆ purgeHistory()

void KisMementoManager::purgeHistory ( KisMementoSP oldestMemento)

Removes all the history that precedes the revision pointed by oldestMemento. That is after calling to purgeHistory(someMemento) you won't be able to do rollback(someMemento) anymore.

Definition at line 353 of file kis_memento_manager.cc.

354{
355 if (m_currentMemento == oldestMemento) {
356 commit();
357 }
358
359 qint32 revisionIndex = findRevisionByMemento(oldestMemento);
360 if (revisionIndex < 0) return;
361
362 for(; revisionIndex > 0; revisionIndex--) {
363 resetRevisionHistory(m_revisions.first().itemList);
364 m_revisions.removeFirst();
365 }
366
367 KIS_ASSERT(m_revisions.first().memento == oldestMemento);
368 resetRevisionHistory(m_revisions.first().itemList);
369
370 DEBUG_DUMP_MESSAGE("PURGE_HISTORY");
371}
qint32 findRevisionByMemento(KisMementoSP memento) const
void resetRevisionHistory(KisMementoItemList list)

References commit(), DEBUG_DUMP_MESSAGE, findRevisionByMemento(), KIS_ASSERT, m_currentMemento, m_revisions, and resetRevisionHistory().

◆ registerTileChange()

void KisMementoManager::registerTileChange ( KisTile * tile)

Most tricky part. This function is called by a tile, when it gets new tile-data through COW. The Memento Manager wraps this tile-data into KisMementoItem class and waits until commit() order given. By this time KisMementoItem doesn't take part in COW mechanism. It only holds tileData->m_refCount counter to ensure tile isn't deleted from memory. When commit() comes, KisMementoItem grabs tileData->m_usersCount and since that moment it is a rightful co-owner of the tileData and COW participant. It means that tileData won't be ever changed since then. Every write request to the original tile will lead to duplicating tileData and registering it here again...

NOTE: We don't assume that the registerTileChange/Delete can be called once a commit only. Reverse can happen when we do sequential clears of the device. In such a case the tiles will be removed and added several times during a commit.

TODO: There is an 'uncomfortable' state for the tile possible 1) Imagine we have a clear device 2) Then we painted something in a tile 3) It registered itself using registerTileChange() 4) Then we called clear() and getMemento() [==commit()] 5) The tile will be registered as deleted and successfully committed to a revision. That means the states of the memento manager at stages 1 and 5 do not coincide. This will not lead to any memory leaks or bugs seen, it just not good from a theoretical perspective.

Definition at line 113 of file kis_memento_manager.cc.

114{
115 if (registrationBlocked()) return;
116
117 DEBUG_LOG_TILE_ACTION("reg. [C]", tile, tile->col(), tile->row());
118
119 KisMementoItemSP mi = m_index.getExistingTile(tile->col(), tile->row());
120
121 if(!mi) {
122 mi = new KisMementoItem();
123 mi->changeTile(tile);
124 m_index.addTile(mi);
125
128 }
129 }
130 else {
131 mi->reset();
132 mi->changeTile(tile);
133 }
134}
void changeTile(KisTile *tile)
void updateExtent(qint32 col, qint32 row, QMutex *currentMementoExtentLock)
Definition kis_memento.h:88
TileTypeSP getExistingTile(qint32 col, qint32 row)
void addTile(TileTypeSP tile)
qint32 row() const
Definition kis_tile.h:92
qint32 col() const
Definition kis_tile.h:95
#define DEBUG_LOG_TILE_ACTION(action, tile, col, row)
#define registrationBlocked()

References KisTileHashTableTraits< T >::addTile(), KisMementoItem::changeTile(), KisMementoItem::col(), KisTile::col(), DEBUG_LOG_TILE_ACTION, KisTileHashTableTraits< T >::getExistingTile(), m_currentMemento, m_currentMementoExtentLock, m_index, namedTransactionInProgress, registrationBlocked, KisMementoItem::reset(), KisMementoItem::row(), KisTile::row(), and KisMemento::updateExtent().

◆ registerTileDeleted()

void KisMementoManager::registerTileDeleted ( KisTile * tile)

Called when a tile deleted. Creates empty KisMementoItem showing that there was a tile one day

Definition at line 136 of file kis_memento_manager.cc.

137{
138 if (registrationBlocked()) return;
139
140 DEBUG_LOG_TILE_ACTION("reg. [D]", tile, tile->col(), tile->row());
141
142 KisMementoItemSP mi = m_index.getExistingTile(tile->col(), tile->row());
143
144 if(!mi) {
145 mi = new KisMementoItem();
146
148 mi->deleteTile(tile, defaultTileData);
149 defaultTileData->deref();
150
151 m_index.addTile(mi);
152
155 }
156 }
157 else {
158 mi->reset();
159
161 mi->deleteTile(tile, defaultTileData);
162 defaultTileData->deref();
163 }
164}
void deleteTile(KisTile *tile, KisTileData *defaultTileData)
KisTileData * refAndFetchDefaultTileData() const

References KisTileHashTableTraits< T >::addTile(), KisMementoItem::col(), KisTile::col(), DEBUG_LOG_TILE_ACTION, KisMementoItem::deleteTile(), KisTileData::deref(), KisTileHashTableTraits< T >::getExistingTile(), m_currentMemento, m_currentMementoExtentLock, m_headsHashTable, m_index, namedTransactionInProgress, KisTileHashTableTraits< T >::refAndFetchDefaultTileData(), registrationBlocked, KisMementoItem::reset(), KisMementoItem::row(), KisTile::row(), and KisMemento::updateExtent().

◆ resetRevisionHistory()

void KisMementoManager::resetRevisionHistory ( KisMementoItemList list)
protected

Definition at line 385 of file kis_memento_manager.cc.

386{
387 KisMementoItemSP parentMI;
389
390 Q_FOREACH (mi, list) {
391 parentMI = mi->parent();
392 if(!parentMI) continue;
393
394 while (parentMI->parent()) {
395 parentMI = parentMI->parent();
396 }
397 mi->setParent(parentMI);
398 }
399}
KisMementoItemSP parent()

References KisMementoItem::parent(), and KisMementoItem::setParent().

◆ rollback()

void KisMementoManager::rollback ( KisTileHashTable * ht,
KisMementoSP memento )

Undo and Redo stuff respectively.

When calling them, INDEX list should be empty, so to say, "working copy should be clean".

NOTE: tricky hack alert. We have just deleted some tiles from the original hash table. And they accurately reported to us about their death. Should have reported... But we have prevented their registration with explicitly blocking the process. So all the dead tiles are going to /dev/null :)

PS: It could cause some race condition... But we insist on serialization of rollback()/rollforward() requests. There is not much sense in calling rollback() concurrently.

Definition at line 265 of file kis_memento_manager.cc.

266{
267 commit();
268
269 if (! m_revisions.size()) return;
270
271 KisHistoryItem changeList = m_revisions.takeLast();
272
273 // SANITY CHECK: the transaction's memento must be in sync with
274 // the revisions list we have locally
275 KIS_SAFE_ASSERT_RECOVER_NOOP(changeList.memento == memento);
276
278 KisMementoItemSP parentMI;
279 KisMementoItemList::iterator iter;
280
282 forEachReversed(iter, changeList.itemList) {
283 mi=*iter;
284 parentMI = mi->parent();
285
286 if (mi->type() == KisMementoItem::CHANGED)
287 ht->deleteTile(mi->col(), mi->row());
288 if (parentMI->type() == KisMementoItem::CHANGED)
289 ht->addTile(parentMI->tile(this));
290
291 m_headsHashTable.deleteTile(parentMI->col(), parentMI->row());
292 m_headsHashTable.addTile(parentMI);
293
294 // This is not necessary
295 //mi->setParent(0);
296 }
310
311 // We have just emulated a commit so:
314
315 m_cancelledRevisions.prepend(changeList);
316 DEBUG_DUMP_MESSAGE("UNDONE");
317
318 // Waking up pooler to prepare copies for us
320}
#define forEachReversed(iter, list)
#define blockRegistration()
#define unblockRegistration()

References KisTileHashTableTraits< T >::addTile(), blockRegistration, KisMementoItem::CHANGED, KisMementoItem::col(), commit(), DEBUG_DUMP_MESSAGE, KisTileHashTableTraits< T >::deleteTile(), forEachReversed, KisTileDataStore::instance(), KisHistoryItem::itemList, KisTileDataStore::kickPooler(), KIS_ASSERT, KIS_SAFE_ASSERT_RECOVER_NOOP, m_cancelledRevisions, m_currentMemento, m_headsHashTable, m_revisions, KisHistoryItem::memento, namedTransactionInProgress, KisMementoItem::parent(), KisMementoItem::row(), KisMementoItem::tile(), KisMementoItem::type(), and unblockRegistration.

◆ rollforward()

void KisMementoManager::rollforward ( KisTileHashTable * ht,
KisMementoSP memento )

Definition at line 322 of file kis_memento_manager.cc.

323{
325
326 if (!m_cancelledRevisions.size()) return;
327
328 KisHistoryItem changeList = m_cancelledRevisions.takeFirst();
329
330 // SANITY CHECK: the transaction's memento must be in sync with
331 // the revisions list we have locally
332 KIS_SAFE_ASSERT_RECOVER_NOOP(changeList.memento == memento);
333
335
337 Q_FOREACH (mi, changeList.itemList) {
338 if (mi->parent()->type() == KisMementoItem::CHANGED)
339 ht->deleteTile(mi->col(), mi->row());
340 if (mi->type() == KisMementoItem::CHANGED)
341 ht->addTile(mi->tile(this));
342
343 m_index.addTile(mi);
344 }
345 // see comment in rollback()
346
347 m_currentMemento = changeList.memento;
348 commit();
350 DEBUG_DUMP_MESSAGE("REDONE");
351}
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128

References KisTileHashTableTraits< T >::addTile(), blockRegistration, KisMementoItem::CHANGED, KisMementoItem::col(), commit(), DEBUG_DUMP_MESSAGE, KisTileHashTableTraits< T >::deleteTile(), KisTileHashTableTraits< T >::isEmpty(), KisHistoryItem::itemList, KIS_SAFE_ASSERT_RECOVER_NOOP, KIS_SAFE_ASSERT_RECOVER_RETURN, m_cancelledRevisions, m_currentMemento, m_index, KisHistoryItem::memento, KisMementoItem::parent(), KisMementoItem::row(), KisMementoItem::tile(), KisMementoItem::type(), and unblockRegistration.

◆ setDefaultTileData()

void KisMementoManager::setDefaultTileData ( KisTileData * defaultTileData)

Definition at line 401 of file kis_memento_manager.cc.

402{
403 m_headsHashTable.setDefaultTileData(defaultTileData);
404 m_index.setDefaultTileData(defaultTileData);
405}
void setDefaultTileData(KisTileData *defaultTileData)

References m_headsHashTable, m_index, and KisTileHashTableTraits< T >::setDefaultTileData().

Member Data Documentation

◆ m_cancelledRevisions

KisHistoryList KisMementoManager::m_cancelledRevisions
protected

List of revisions temporarily undone while rollback()

Definition at line 138 of file kis_memento_manager.h.

◆ m_currentMemento

KisMementoSP KisMementoManager::m_currentMemento
protected

Stores extent of current INDEX. It is the "name" of current named transaction

Definition at line 150 of file kis_memento_manager.h.

◆ m_currentMementoExtentLock

QMutex KisMementoManager::m_currentMementoExtentLock
protected

Definition at line 151 of file kis_memento_manager.h.

◆ m_headsHashTable

KisMementoItemHashTable KisMementoManager::m_headsHashTable
protected

A hash table, that stores the most recently updated versions of tiles. Say, HEAD revision :)

Definition at line 144 of file kis_memento_manager.h.

◆ m_index

KisMementoItemHashTable KisMementoManager::m_index
protected

INDEX of tiles to be committed with next commit() We use a hash table to be able to check that we have the only memento item for a tile per commit efficiently

Definition at line 128 of file kis_memento_manager.h.

◆ m_registrationBlocked

bool KisMementoManager::m_registrationBlocked
protected

The flag that blocks registration of changes on tiles. This is a temporary state of the memento manager, that is used for traveling in history

See also
rollback()
rollforward()

Definition at line 161 of file kis_memento_manager.h.

◆ m_revisions

KisHistoryList KisMementoManager::m_revisions
protected

Main list that stores every commit ever done

Definition at line 133 of file kis_memento_manager.h.


The documentation for this class was generated from the following files: