File indexing completed on 2024-12-22 04:10:31

0001 /*
0002  *  SPDX-FileCopyrightText: 2009 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include <QtGlobal>
0008 #include "kis_memento_manager.h"
0009 #include "kis_memento.h"
0010 
0011 
0012 //#define DEBUG_MM
0013 
0014 #ifdef DEBUG_MM
0015 #define DEBUG_LOG_TILE_ACTION(action, tile, col, row)                   \
0016     printf("### MementoManager (0x%X): %s "             \
0017            "\ttile:\t0x%X (%d, %d) ###\n", (quintptr)this, action,  \
0018            (quintptr)tile, col, row)
0019 
0020 #define DEBUG_LOG_SIMPLE_ACTION(action)                 \
0021     printf("### MementoManager (0x%X): %s\n", (quintptr)this, action)
0022 
0023 #define DEBUG_DUMP_MESSAGE(action) do {                                 \
0024         printf("\n### MementoManager (0x%X): %s \t\t##########\n",  \
0025                (quintptr)this, action);                                 \
0026         debugPrintInfo();                                               \
0027         printf("##################################################################\n\n"); \
0028     } while(0)
0029 #else
0030 
0031 #define DEBUG_LOG_TILE_ACTION(action, tile, col, row)
0032 #define DEBUG_LOG_SIMPLE_ACTION(action)
0033 #define DEBUG_DUMP_MESSAGE(action)
0034 #endif
0035 
0036 
0037 /**
0038  * The class is supposed to store the changes of the paint device
0039  * it is associated with. The history of changes is presented in form
0040  * of transactions (revisions). If you purge the history of one
0041  * transaction (revision) with purgeHistory() we won't be able to undo
0042  * the changes made by this transactions.
0043  *
0044  * The Memento Manager can be in two states:
0045  *     - Named Transaction is in progress - it means the caller
0046  *       has explicitly requested creation of a new transaction.
0047  *       The handle for the transaction is stored on a side of
0048  *       the caller. And the history will be automatically purged
0049  *       when the handler dies.
0050  *     - Anonymous Transaction is in progress - the caller isn't
0051  *       bothered about transactions at all. We pretend as we do
0052  *       not support any versioning and do not have any historical
0053  *       information. The history of such transactions is not purged
0054  *       automatically, but it is free'd when younger named transaction
0055  *       is purged.
0056  */
0057 
0058 #define blockRegistration() (m_registrationBlocked = true)
0059 #define unblockRegistration() (m_registrationBlocked = false)
0060 #define registrationBlocked() (m_registrationBlocked)
0061 
0062 #define namedTransactionInProgress() ((bool)m_currentMemento)
0063 
0064 KisMementoManager::KisMementoManager()
0065     : m_index(0),
0066       m_headsHashTable(0),
0067       m_registrationBlocked(false)
0068 {
0069     /**
0070      * Tile change/delete registration is enabled for all
0071      * devices by default. It can't be delayed.
0072      */
0073 }
0074 
0075 KisMementoManager::KisMementoManager(const KisMementoManager& rhs)
0076     : m_index(rhs.m_index, 0),
0077         m_revisions(rhs.m_revisions),
0078         m_cancelledRevisions(rhs.m_cancelledRevisions),
0079         m_headsHashTable(rhs.m_headsHashTable, 0),
0080         m_currentMemento(rhs.m_currentMemento),
0081         m_registrationBlocked(rhs.m_registrationBlocked)
0082 {
0083     Q_ASSERT_X(!m_registrationBlocked,
0084                "KisMementoManager", "(impossible happened) "
0085                "The device has been copied while registration was blocked");
0086 }
0087 
0088 KisMementoManager::~KisMementoManager()
0089 {
0090     // Nothing to be done here. Happily...
0091     // Everything is done by QList and KisSharedPtr...
0092     DEBUG_LOG_SIMPLE_ACTION("died\n");
0093 }
0094 
0095 /**
0096  * NOTE: We don't assume that the registerTileChange/Delete
0097  * can be called once a commit only. Reverse can happen when we
0098  * do sequential clears of the device. In such a case the tiles
0099  * will be removed and added several times during a commit.
0100  *
0101  * TODO: There is an 'uncomfortable' state for the tile possible
0102  * 1) Imagine we have a clear device
0103  * 2) Then we painted something in a tile
0104  * 3) It registered itself using registerTileChange()
0105  * 4) Then we called clear() and getMemento() [==commit()]
0106  * 5) The tile will be registered as deleted and successfully
0107  *    committed to a revision. That means the states of the memento
0108  *    manager at stages 1 and 5 do not coincide.
0109  * This will not lead to any memory leaks or bugs seen, it just
0110  * not good from a theoretical perspective.
0111  */
0112 
0113 void KisMementoManager::registerTileChange(KisTile *tile)
0114 {
0115     if (registrationBlocked()) return;
0116 
0117     DEBUG_LOG_TILE_ACTION("reg. [C]", tile, tile->col(), tile->row());
0118 
0119     KisMementoItemSP mi = m_index.getExistingTile(tile->col(), tile->row());
0120 
0121     if(!mi) {
0122         mi = new KisMementoItem();
0123         mi->changeTile(tile);
0124         m_index.addTile(mi);
0125 
0126         if(namedTransactionInProgress()) {
0127             m_currentMemento->updateExtent(mi->col(), mi->row(), &m_currentMementoExtentLock);
0128         }
0129     }
0130     else {
0131         mi->reset();
0132         mi->changeTile(tile);
0133     }
0134 }
0135 
0136 void KisMementoManager::registerTileDeleted(KisTile *tile)
0137 {
0138     if (registrationBlocked()) return;
0139 
0140     DEBUG_LOG_TILE_ACTION("reg. [D]", tile, tile->col(), tile->row());
0141 
0142     KisMementoItemSP mi = m_index.getExistingTile(tile->col(), tile->row());
0143 
0144     if(!mi) {
0145         mi = new KisMementoItem();
0146 
0147         KisTileData *defaultTileData = m_headsHashTable.refAndFetchDefaultTileData();
0148         mi->deleteTile(tile, defaultTileData);
0149         defaultTileData->deref();
0150 
0151         m_index.addTile(mi);
0152 
0153         if(namedTransactionInProgress()) {
0154             m_currentMemento->updateExtent(mi->col(), mi->row(), &m_currentMementoExtentLock);
0155         }
0156     }
0157     else {
0158         mi->reset();
0159 
0160         KisTileData *defaultTileData = m_headsHashTable.refAndFetchDefaultTileData();
0161         mi->deleteTile(tile, defaultTileData);
0162         defaultTileData->deref();
0163     }
0164 }
0165 
0166 void KisMementoManager::commit()
0167 {
0168     if (m_index.isEmpty()) {
0169         if(namedTransactionInProgress()) {
0170             //warnTiles << "Named Transaction is empty";
0171             /**
0172              * We still need to continue commit, because
0173              * a named transaction may be reverted by the user
0174              */
0175         }
0176         else {
0177             m_currentMemento = 0;
0178             return;
0179         }
0180     }
0181 
0182     KisMementoItemList revisionList;
0183     KisMementoItemSP mi;
0184     KisMementoItemSP parentMI;
0185     bool newTile;
0186 
0187     KisMementoItemHashTableIterator iter(&m_index);
0188     while ((mi = iter.tile())) {
0189         parentMI = m_headsHashTable.getTileLazy(mi->col(), mi->row(), newTile);
0190 
0191         mi->setParent(parentMI);
0192         mi->commit();
0193         revisionList.append(mi);
0194 
0195         m_headsHashTable.deleteTile(mi->col(), mi->row());
0196 
0197         iter.moveCurrentToHashTable(&m_headsHashTable);
0198         //iter.next(); // previous line does this for us
0199     }
0200 
0201     KisHistoryItem hItem;
0202     hItem.itemList = revisionList;
0203     hItem.memento = m_currentMemento.data();
0204     m_revisions.append(hItem);
0205 
0206     m_currentMemento = 0;
0207     KIS_ASSERT(m_index.isEmpty());
0208 
0209     DEBUG_DUMP_MESSAGE("COMMIT_DONE");
0210 
0211     // Waking up pooler to prepare copies for us
0212     KisTileDataStore::instance()->kickPooler();
0213 }
0214 
0215 KisTileSP KisMementoManager::getCommittedTile(qint32 col, qint32 row, bool &existingTile)
0216 {
0217     /**
0218      * Our getOldTile mechanism is supposed to return current
0219      * tile, if the history is disabled. So we return zero if
0220      * no named transaction is in progress.
0221      */
0222     if(!namedTransactionInProgress())
0223         return KisTileSP();
0224 
0225     KisMementoItemSP mi = m_headsHashTable.getReadOnlyTileLazy(col, row, existingTile);
0226     return mi->tile(0);
0227 }
0228 
0229 KisMementoSP KisMementoManager::getMemento()
0230 {
0231     /**
0232      * We do not allow nested transactions
0233      */
0234     KIS_SAFE_ASSERT_RECOVER_NOOP(!namedTransactionInProgress());
0235 
0236     /**
0237      * The following assert is useful for testing if some code creates a
0238      * transaction on a device with "inconsistent history". We cannot keep
0239      * this sanity check enabled all the time, because in some places
0240      * (e.g. projection in KisAsyncMerger) such usecase is considered legit.
0241      * But in places with "consistent history", e.g. in layer's paint
0242      * device, such usage will cause undo corruption.
0243      */
0244     // KIS_SAFE_ASSERT_RECOVER_NOOP(m_index.isEmpty());
0245 
0246     // Clear redo() information
0247     m_cancelledRevisions.clear();
0248 
0249     commit();
0250     m_currentMemento = new KisMemento(this);
0251 
0252     DEBUG_LOG_SIMPLE_ACTION("GET_MEMENTO_DONE");
0253 
0254     return m_currentMemento;
0255 }
0256 
0257 KisMementoSP KisMementoManager::currentMemento() {
0258     return m_currentMemento;
0259 }
0260 
0261 #define forEachReversed(iter, list) \
0262         for(iter=list.end(); iter-- != list.begin();)
0263 
0264 
0265 void KisMementoManager::rollback(KisTileHashTable *ht, KisMementoSP memento)
0266 {
0267     commit();
0268 
0269     if (! m_revisions.size()) return;
0270 
0271     KisHistoryItem changeList = m_revisions.takeLast();
0272 
0273     // SANITY CHECK: the transaction's memento must be in sync with
0274     //               the revisions list we have locally
0275     KIS_SAFE_ASSERT_RECOVER_NOOP(changeList.memento == memento);
0276 
0277     KisMementoItemSP mi;
0278     KisMementoItemSP parentMI;
0279     KisMementoItemList::iterator iter;
0280 
0281     blockRegistration();
0282     forEachReversed(iter, changeList.itemList) {
0283         mi=*iter;
0284         parentMI = mi->parent();
0285 
0286         if (mi->type() == KisMementoItem::CHANGED)
0287             ht->deleteTile(mi->col(), mi->row());
0288         if (parentMI->type() == KisMementoItem::CHANGED)
0289             ht->addTile(parentMI->tile(this));
0290 
0291         m_headsHashTable.deleteTile(parentMI->col(), parentMI->row());
0292         m_headsHashTable.addTile(parentMI);
0293 
0294         // This is not necessary
0295         //mi->setParent(0);
0296     }
0297     /**
0298      * NOTE: tricky hack alert.
0299      * We have just deleted some tiles from the original hash table.
0300      * And they accurately reported to us about their death. Should
0301      * have reported... But we have prevented their registration with
0302      * explicitly blocking the process. So all the dead tiles are
0303      * going to /dev/null :)
0304      *
0305      * PS: It could cause some race condition... But we insist on
0306      * serialization  of rollback()/rollforward() requests. There is
0307      * not much sense in calling rollback() concurrently.
0308      */
0309     unblockRegistration();
0310 
0311     // We have just emulated a commit so:
0312     m_currentMemento = 0;
0313     KIS_ASSERT(!namedTransactionInProgress());
0314 
0315     m_cancelledRevisions.prepend(changeList);
0316     DEBUG_DUMP_MESSAGE("UNDONE");
0317 
0318     // Waking up pooler to prepare copies for us
0319     KisTileDataStore::instance()->kickPooler();
0320 }
0321 
0322 void KisMementoManager::rollforward(KisTileHashTable *ht, KisMementoSP memento)
0323 {
0324     KIS_SAFE_ASSERT_RECOVER_RETURN(m_index.isEmpty());
0325 
0326     if (!m_cancelledRevisions.size()) return;
0327 
0328     KisHistoryItem changeList = m_cancelledRevisions.takeFirst();
0329 
0330     // SANITY CHECK: the transaction's memento must be in sync with
0331     //               the revisions list we have locally
0332     KIS_SAFE_ASSERT_RECOVER_NOOP(changeList.memento == memento);
0333 
0334     KisMementoItemSP mi;
0335 
0336     blockRegistration();
0337     Q_FOREACH (mi, changeList.itemList) {
0338         if (mi->parent()->type() == KisMementoItem::CHANGED)
0339             ht->deleteTile(mi->col(), mi->row());
0340         if (mi->type() == KisMementoItem::CHANGED)
0341             ht->addTile(mi->tile(this));
0342 
0343         m_index.addTile(mi);
0344     }
0345     // see comment in rollback()
0346 
0347     m_currentMemento = changeList.memento;
0348     commit();
0349     unblockRegistration();
0350     DEBUG_DUMP_MESSAGE("REDONE");
0351 }
0352 
0353 void KisMementoManager::purgeHistory(KisMementoSP oldestMemento)
0354 {
0355     if (m_currentMemento == oldestMemento) {
0356         commit();
0357     }
0358 
0359     qint32 revisionIndex = findRevisionByMemento(oldestMemento);
0360     if (revisionIndex < 0) return;
0361 
0362     for(; revisionIndex > 0; revisionIndex--) {
0363         resetRevisionHistory(m_revisions.first().itemList);
0364         m_revisions.removeFirst();
0365     }
0366 
0367     KIS_ASSERT(m_revisions.first().memento == oldestMemento);
0368     resetRevisionHistory(m_revisions.first().itemList);
0369 
0370     DEBUG_DUMP_MESSAGE("PURGE_HISTORY");
0371 }
0372 
0373 qint32 KisMementoManager::findRevisionByMemento(KisMementoSP memento) const
0374 {
0375     qint32 index = -1;
0376     for(qint32 i = 0; i < m_revisions.size(); i++) {
0377         if (m_revisions[i].memento == memento) {
0378             index = i;
0379             break;
0380         }
0381     }
0382     return index;
0383 }
0384 
0385 void KisMementoManager::resetRevisionHistory(KisMementoItemList list)
0386 {
0387     KisMementoItemSP parentMI;
0388     KisMementoItemSP mi;
0389 
0390     Q_FOREACH (mi, list) {
0391         parentMI = mi->parent();
0392         if(!parentMI) continue;
0393 
0394         while (parentMI->parent()) {
0395             parentMI = parentMI->parent();
0396         }
0397         mi->setParent(parentMI);
0398     }
0399 }
0400 
0401 void KisMementoManager::setDefaultTileData(KisTileData *defaultTileData)
0402 {
0403     m_headsHashTable.setDefaultTileData(defaultTileData);
0404     m_index.setDefaultTileData(defaultTileData);
0405 }
0406 
0407 void KisMementoManager::debugPrintInfo()
0408 {
0409     printf("KisMementoManager stats:\n");
0410     printf("Index list\n");
0411     KisMementoItemSP mi;
0412     KisMementoItemHashTableIteratorConst iter(&m_index);
0413 
0414     while ((mi = iter.tile())) {
0415         mi->debugPrintInfo();
0416         iter.next();
0417     }
0418 
0419     printf("Revisions list:\n");
0420     qint32 i = 0;
0421     Q_FOREACH (const KisHistoryItem &changeList, m_revisions) {
0422         printf("--- revision #%d ---\n", i++);
0423         Q_FOREACH (mi, changeList.itemList) {
0424             mi->debugPrintInfo();
0425         }
0426     }
0427 
0428     printf("\nCancelled revisions list:\n");
0429     i = 0;
0430     Q_FOREACH (const KisHistoryItem &changeList, m_cancelledRevisions) {
0431         printf("--- revision #%d ---\n", m_revisions.size() + i++);
0432         Q_FOREACH (mi, changeList.itemList) {
0433             mi->debugPrintInfo();
0434         }
0435     }
0436 
0437     printf("----------------\n");
0438     m_headsHashTable.debugPrintInfo();
0439 }