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

0001 /*
0002  *  SPDX-FileCopyrightText: 2018 Andrey Kamakin <a.kamakin@icloud.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #ifndef KIS_TILEHASHTABLE_2_H
0008 #define KIS_TILEHASHTABLE_2_H
0009 
0010 #include "kis_shared.h"
0011 #include "kis_shared_ptr.h"
0012 #include "3rdparty/lock_free_map/concurrent_map.h"
0013 #include "kis_tile.h"
0014 #include "kis_debug.h"
0015 
0016 #define SANITY_CHECK
0017 
0018 /**
0019  * This is a  template for a hash table that stores  tiles (or some other
0020  * objects  resembling tiles).   Actually, this  object should  only have
0021  * col()/row() methods and be able to answer notifyDetachedFromDataManager() requests to
0022  * be   stored   here.    It   is   used   in   KisTiledDataManager   and
0023  * KisMementoManager.
0024  *
0025  * How to use:
0026  *   1) each hash must be unique, otherwise tiles would rewrite each-other
0027  *   2) 0 key is reserved, so can't be used
0028  *   3) col and row must be less than 0x7FFF to guarantee uniqueness of hash for each pair
0029  */
0030 
0031 template <class T>
0032 class KisTileHashTableIteratorTraits2;
0033 
0034 template <class T>
0035 class KisTileHashTableTraits2
0036 {
0037     static constexpr bool isInherited = std::is_convertible<T*, KisShared*>::value;
0038     Q_STATIC_ASSERT_X(isInherited, "Template must inherit KisShared");
0039 
0040 public:
0041     typedef T TileType;
0042     typedef KisSharedPtr<T> TileTypeSP;
0043     typedef KisWeakSharedPtr<T> TileTypeWSP;
0044 
0045     KisTileHashTableTraits2(KisMementoManager *mm);
0046     KisTileHashTableTraits2(const KisTileHashTableTraits2<T> &ht, KisMementoManager *mm);
0047     ~KisTileHashTableTraits2();
0048 
0049     bool isEmpty()
0050     {
0051         return !m_numTiles.load();
0052     }
0053 
0054     bool tileExists(qint32 col, qint32 row);
0055 
0056     /**
0057      * Returns a tile in position (col,row). If no tile exists,
0058      * returns null.
0059      * \param col column of the tile
0060      * \param row row of the tile
0061      */
0062     TileTypeSP getExistingTile(qint32 col, qint32 row);
0063 
0064     /**
0065      * Returns a tile in position (col,row). If no tile exists,
0066      * creates a new one, attaches it to the list and returns.
0067      * \param col column of the tile
0068      * \param row row of the tile
0069      * \param newTile out-parameter, returns true if a new tile
0070      *                was created
0071      */
0072     TileTypeSP getTileLazy(qint32 col, qint32 row, bool& newTile);
0073 
0074     /**
0075      * Returns a tile in position (col,row). If no tile exists,
0076      * creates nothing, but returns shared default tile object
0077      * of the table. Be careful, this object has column and row
0078      * parameters set to (qint32_MIN, qint32_MIN).
0079      * \param col column of the tile
0080      * \param row row of the tile
0081      * \param existingTile returns true if the tile actually exists in the table
0082      *                     and it is not a lazily created default wrapper tile
0083      */
0084     TileTypeSP getReadOnlyTileLazy(qint32 col, qint32 row, bool &existingTile);
0085     void addTile(TileTypeSP tile);
0086     bool deleteTile(TileTypeSP tile);
0087     bool deleteTile(qint32 col, qint32 row);
0088 
0089     void clear();
0090 
0091     void setDefaultTileData(KisTileData *defaultTileData);
0092     KisTileData* defaultTileData();
0093 
0094     /**
0095      * Returns a pointer to the default tile data object with ref counter
0096      * increased by one. Make sure you call deref() after you finished using
0097      * this object.
0098      */
0099     KisTileData* refAndFetchDefaultTileData();
0100 
0101 
0102     qint32 numTiles()
0103     {
0104         return m_numTiles.load();
0105     }
0106 
0107     void debugPrintInfo();
0108     void debugMaxListLength(qint32 &min, qint32 &max);
0109 
0110     friend class KisTileHashTableIteratorTraits2<T>;
0111 
0112 private:
0113     struct MemoryReclaimer {
0114         MemoryReclaimer(TileType *data) : d(data) {}
0115 
0116         void destroy()
0117         {
0118             TileTypeSP::deref(reinterpret_cast<TileTypeSP*>(this), d);
0119             delete this;
0120         }
0121 
0122     private:
0123         TileType *d;
0124     };
0125 
0126     inline quint32 calculateHashImpl(qint32 col, qint32 row)
0127     {
0128         if (col == 0 && row == 0) {
0129             col = 0x7FFF;
0130             row = 0x7FFF;
0131         }
0132 
0133         return ((static_cast<quint32>(row) << 16) | (static_cast<quint32>(col) & 0xFFFF));
0134     }
0135 
0136     inline quint32 calculateHash(qint32 col, qint32 row)
0137     {
0138 #ifdef SANITY_CHECK
0139         KIS_ASSERT_RECOVER_NOOP(qAbs(row) < 0x7FFF && qAbs(col) < 0x7FFF);
0140 #endif // SANITY_CHECK
0141 
0142         return calculateHashImpl(col, row);
0143     }
0144 
0145     /**
0146      * A version of the hash function that returns an invalid hash in
0147      * case the requested tile is out of range
0148      */
0149     inline quint32 calculateHashSafe(qint32 col, qint32 row)
0150     {
0151         KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(qAbs(row) < 0x7FFF && qAbs(col) < 0x7FFF, 0);
0152         return calculateHashImpl(col, row);
0153     }
0154 
0155     inline void insert(quint32 idx, TileTypeSP item)
0156     {
0157         TileTypeSP::ref(&item, item.data());
0158         TileType *tile = 0;
0159 
0160         {
0161             QReadLocker locker(&m_iteratorLock);
0162             m_map.getGC().lockRawPointerAccess();
0163             tile = m_map.assign(idx, item.data());
0164         }
0165 
0166         if (tile) {
0167             tile->notifyDeadWithoutDetaching();
0168             m_map.getGC().enqueue(&MemoryReclaimer::destroy, new MemoryReclaimer(tile));
0169         } else {
0170             m_numTiles.fetchAndAddRelaxed(1);
0171         }
0172 
0173         m_map.getGC().unlockRawPointerAccess();
0174 
0175         m_map.getGC().update();
0176     }
0177 
0178     inline bool erase(quint32 idx)
0179     {
0180         m_map.getGC().lockRawPointerAccess();
0181 
0182         bool wasDeleted = false;
0183         TileType *tile = m_map.erase(idx);
0184 
0185         if (tile) {
0186             tile->notifyDetachedFromDataManager();
0187 
0188             wasDeleted = true;
0189             m_numTiles.fetchAndSubRelaxed(1);
0190             m_map.getGC().enqueue(&MemoryReclaimer::destroy, new MemoryReclaimer(tile));
0191         }
0192 
0193         m_map.getGC().unlockRawPointerAccess();
0194 
0195         m_map.getGC().update();
0196         return wasDeleted;
0197     }
0198 
0199 private:
0200     typedef ConcurrentMap<quint32, TileType*> LockFreeTileMap;
0201     typedef typename LockFreeTileMap::Mutator LockFreeTileMapMutator;
0202     mutable LockFreeTileMap m_map;
0203 
0204     /**
0205      * We still need something to guard changes in m_defaultTileData,
0206      * otherwise there will be concurrent read/writes, resulting in broken memory.
0207      */
0208     QReadWriteLock m_defaultPixelDataLock;
0209     mutable QReadWriteLock m_iteratorLock;
0210 
0211     QAtomicInt m_numTiles;
0212     KisTileData *m_defaultTileData;
0213     KisMementoManager *m_mementoManager;
0214 };
0215 
0216 template <class T>
0217 class KisTileHashTableIteratorTraits2
0218 {
0219 public:
0220     typedef T TileType;
0221     typedef KisSharedPtr<T> TileTypeSP;
0222     typedef typename ConcurrentMap<quint32, TileType*>::Iterator Iterator;
0223 
0224     KisTileHashTableIteratorTraits2(KisTileHashTableTraits2<T> *ht) : m_ht(ht)
0225     {
0226         m_ht->m_iteratorLock.lockForWrite();
0227         m_iter.setMap(m_ht->m_map);
0228     }
0229 
0230     ~KisTileHashTableIteratorTraits2()
0231     {
0232         m_ht->m_iteratorLock.unlock();
0233     }
0234 
0235     void next()
0236     {
0237         m_iter.next();
0238     }
0239 
0240     TileTypeSP tile() const
0241     {
0242         return TileTypeSP(m_iter.getValue());
0243     }
0244 
0245     bool isDone() const
0246     {
0247         return !m_iter.isValid();
0248     }
0249 
0250     void deleteCurrent()
0251     {
0252         m_ht->erase(m_iter.getKey());
0253         next();
0254     }
0255 
0256     void moveCurrentToHashTable(KisTileHashTableTraits2<T> *newHashTable)
0257     {
0258         TileTypeSP tile = m_iter.getValue();
0259         next();
0260 
0261         quint32 idx = m_ht->calculateHash(tile->col(), tile->row());
0262         m_ht->erase(idx);
0263         newHashTable->insert(idx, tile);
0264     }
0265 
0266 private:
0267     KisTileHashTableTraits2<T> *m_ht;
0268     Iterator m_iter;
0269 };
0270 
0271 template <class T>
0272 KisTileHashTableTraits2<T>::KisTileHashTableTraits2(KisMementoManager *mm)
0273     : m_numTiles(0), m_defaultTileData(0), m_mementoManager(mm)
0274 {
0275 }
0276 
0277 template <class T>
0278 KisTileHashTableTraits2<T>::KisTileHashTableTraits2(const KisTileHashTableTraits2<T> &ht, KisMementoManager *mm)
0279     : KisTileHashTableTraits2(mm)
0280 {
0281     setDefaultTileData(ht.m_defaultTileData);
0282 
0283     QWriteLocker locker(&ht.m_iteratorLock);
0284     typename ConcurrentMap<quint32, TileType*>::Iterator iter(ht.m_map);
0285 
0286     while (iter.isValid()) {
0287         TileTypeSP tile = new TileType(*iter.getValue(), m_mementoManager);
0288         insert(iter.getKey(), tile);
0289         iter.next();
0290     }
0291 }
0292 
0293 template <class T>
0294 KisTileHashTableTraits2<T>::~KisTileHashTableTraits2()
0295 {
0296     clear();
0297     setDefaultTileData(0);
0298 }
0299 
0300 template<class T>
0301 bool KisTileHashTableTraits2<T>::tileExists(qint32 col, qint32 row)
0302 {
0303     return getExistingTile(col, row);
0304 }
0305 
0306 template <class T>
0307 typename KisTileHashTableTraits2<T>::TileTypeSP KisTileHashTableTraits2<T>::getExistingTile(qint32 col, qint32 row)
0308 {
0309     const quint32 idx = calculateHashSafe(col, row);
0310     if (!idx) {
0311         /// a tile with invalid index obviously doesn't exist
0312         return TileTypeSP();
0313     }
0314 
0315     m_map.getGC().lockRawPointerAccess();
0316     TileTypeSP tile = m_map.get(idx);
0317     m_map.getGC().unlockRawPointerAccess();
0318 
0319     m_map.getGC().update();
0320     return tile;
0321 }
0322 
0323 template <class T>
0324 typename KisTileHashTableTraits2<T>::TileTypeSP KisTileHashTableTraits2<T>::getTileLazy(qint32 col, qint32 row, bool &newTile)
0325 {
0326     newTile = false;
0327     const quint32 idx = calculateHashSafe(col, row);
0328     if (!idx) {
0329         /// when invalid tile index is requested, just return a
0330         /// detached tile with the default data
0331 
0332         /// we pretend as if this tile has already existed, it will
0333         /// allow the calling code to avoid modifying the extent
0334         /// manager
0335         newTile = false;
0336 
0337         QReadLocker locker(&m_defaultPixelDataLock);
0338         return new TileType(col, row, m_defaultTileData, 0);
0339     }
0340 
0341     // we are going to assign a raw-pointer tile from the table
0342     // to a shared pointer...
0343     m_map.getGC().lockRawPointerAccess();
0344 
0345     TileTypeSP tile = m_map.get(idx);
0346 
0347     while (!tile) {
0348         // we shouldn't try to acquire **any** lock with
0349         // raw-pointer lock held
0350         m_map.getGC().unlockRawPointerAccess();
0351 
0352         {
0353             QReadLocker locker(&m_defaultPixelDataLock);
0354             tile = new TileType(col, row, m_defaultTileData, 0);
0355         }
0356 
0357         TileTypeSP::ref(&tile, tile.data());
0358         TileType *discardedTile = 0;
0359 
0360         // iterator lock should be taken **before**
0361         // the pointers are locked
0362         m_iteratorLock.lockForRead();
0363 
0364         // and now lock raw-pointers again
0365         m_map.getGC().lockRawPointerAccess();
0366 
0367         // mutator might have become invalidated when
0368         // we released raw pointers, so we need to reinitialize it
0369         LockFreeTileMapMutator mutator = m_map.insertOrFind(idx);
0370         if (!mutator.getValue()) {
0371             discardedTile = mutator.exchangeValue(tile.data());
0372         } else {
0373             discardedTile = tile.data();
0374         }
0375 
0376         m_iteratorLock.unlock();
0377 
0378         if (discardedTile) {
0379             // we've got our tile back, it didn't manage to
0380             // get into the table. Now release the allocated
0381             // tile and push TO/GA switch.
0382             tile = 0;
0383 
0384             discardedTile->notifyDeadWithoutDetaching();
0385             m_map.getGC().enqueue(&MemoryReclaimer::destroy, new MemoryReclaimer(discardedTile));
0386 
0387             tile = m_map.get(idx);
0388             continue;
0389 
0390         } else {
0391             newTile = true;
0392             m_numTiles.fetchAndAddRelaxed(1);
0393 
0394             tile->notifyAttachedToDataManager(m_mementoManager);
0395         }
0396     }
0397     m_map.getGC().unlockRawPointerAccess();
0398 
0399     m_map.getGC().update();
0400     return tile;
0401 }
0402 
0403 template <class T>
0404 typename KisTileHashTableTraits2<T>::TileTypeSP KisTileHashTableTraits2<T>::getReadOnlyTileLazy(qint32 col, qint32 row, bool &existingTile)
0405 {
0406     const quint32 idx = calculateHashSafe(col, row);
0407     if (!idx) {
0408         /// when invalid tile index is requested, just return a
0409         /// detached tile with the default data
0410 
0411         /// we pretend as if this tile hasn't existed, it will
0412         /// allow the calling code to avoid modifying the extent
0413         /// manager (note, that is opposite to what happens in
0414         /// getTileLazy())
0415         existingTile = false;
0416 
0417         QReadLocker locker(&m_defaultPixelDataLock);
0418         return new TileType(col, row, m_defaultTileData, 0);
0419     }
0420 
0421     m_map.getGC().lockRawPointerAccess();
0422     TileTypeSP tile = m_map.get(idx);
0423     m_map.getGC().unlockRawPointerAccess();
0424 
0425     existingTile = tile;
0426 
0427     if (!existingTile) {
0428         QReadLocker locker(&m_defaultPixelDataLock);
0429         tile = new TileType(col, row, m_defaultTileData, 0);
0430     }
0431 
0432     m_map.getGC().update();
0433     return tile;
0434 }
0435 
0436 template <class T>
0437 void KisTileHashTableTraits2<T>::addTile(TileTypeSP tile)
0438 {
0439     quint32 idx = calculateHash(tile->col(), tile->row());
0440     insert(idx, tile);
0441 }
0442 
0443 template <class T>
0444 bool KisTileHashTableTraits2<T>::deleteTile(TileTypeSP tile)
0445 {
0446     return deleteTile(tile->col(), tile->row());
0447 }
0448 
0449 template <class T>
0450 bool KisTileHashTableTraits2<T>::deleteTile(qint32 col, qint32 row)
0451 {
0452     const quint32 idx = calculateHashSafe(col, row);
0453     if (!idx) {
0454         /// when invalid tile index is requested, just do nothing
0455         return false;
0456     }
0457 
0458     return erase(idx);
0459 }
0460 
0461 template<class T>
0462 void KisTileHashTableTraits2<T>::clear()
0463 {
0464     {
0465         QWriteLocker locker(&m_iteratorLock);
0466 
0467         typename ConcurrentMap<quint32, TileType*>::Iterator iter(m_map);
0468         TileType *tile = 0;
0469 
0470         while (iter.isValid()) {
0471             m_map.getGC().lockRawPointerAccess();
0472             tile = m_map.erase(iter.getKey());
0473 
0474             if (tile) {
0475                 tile->notifyDetachedFromDataManager();
0476                 m_map.getGC().enqueue(&MemoryReclaimer::destroy, new MemoryReclaimer(tile));
0477             }
0478             m_map.getGC().unlockRawPointerAccess();
0479 
0480             iter.next();
0481         }
0482 
0483         m_numTiles.store(0);
0484     }
0485 
0486     // garbage collection must **not** be run with locks held
0487     m_map.getGC().update();
0488 }
0489 
0490 template <class T>
0491 inline void KisTileHashTableTraits2<T>::setDefaultTileData(KisTileData *defaultTileData)
0492 {
0493     QWriteLocker locker(&m_defaultPixelDataLock);
0494 
0495     if (m_defaultTileData) {
0496         m_defaultTileData->release();
0497         m_defaultTileData = 0;
0498     }
0499 
0500     if (defaultTileData) {
0501         defaultTileData->acquire();
0502         m_defaultTileData = defaultTileData;
0503     }
0504 }
0505 
0506 template <class T>
0507 inline KisTileData* KisTileHashTableTraits2<T>::defaultTileData()
0508 {
0509     QReadLocker locker(&m_defaultPixelDataLock);
0510     return m_defaultTileData;
0511 }
0512 
0513 template <class T>
0514 inline KisTileData* KisTileHashTableTraits2<T>::refAndFetchDefaultTileData()
0515 {
0516     QReadLocker locker(&m_defaultPixelDataLock);
0517     m_defaultTileData->ref();
0518     return m_defaultTileData;
0519 }
0520 
0521 
0522 template <class T>
0523 void KisTileHashTableTraits2<T>::debugPrintInfo()
0524 {
0525 }
0526 
0527 template <class T>
0528 void KisTileHashTableTraits2<T>::debugMaxListLength(qint32 &/*min*/, qint32 &/*max*/)
0529 {
0530 }
0531 
0532 typedef KisTileHashTableTraits2<KisTile> KisTileHashTable;
0533 typedef KisTileHashTableIteratorTraits2<KisTile> KisTileHashTableIterator;
0534 typedef KisTileHashTableIteratorTraits2<KisTile> KisTileHashTableConstIterator;
0535 
0536 #endif // KIS_TILEHASHTABLE_2_H