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