File indexing completed on 2024-05-12 04:37:56
0001 /* 0002 SPDX-FileCopyrightText: 2008 David Nolden <david.nolden.kdevelop@art-master.de> 0003 0004 SPDX-License-Identifier: LGPL-2.0-only 0005 */ 0006 0007 #ifndef KDEVPLATFORM_APPENDEDLIST_H 0008 #define KDEVPLATFORM_APPENDEDLIST_H 0009 0010 #include <QList> 0011 #include <QMutex> 0012 #include <QVector> 0013 0014 #include <util/kdevvarlengtharray.h> 0015 #include <util/stack.h> 0016 0017 #include <ctime> 0018 #include <iostream> 0019 0020 namespace KDevelop { 0021 class AbstractItemRepository; 0022 /** 0023 * This file contains macros and classes that can be used to conveniently implement classes that store the data of an arbitrary count 0024 * of additional lists within the same memory block directly behind the class data, in a way that one the whole data can be stored by one copy-operation 0025 * to another place, like needed in ItemRepository. These macros simplify having two versions of a class: One that has its lists attached in memory, 0026 * and one version that has them contained as a directly accessible KDevVarLengthArray. Both versions have their lists accessible through access-functions, 0027 * have a completeSize() function that computes the size of the one-block version, and a copyListsFrom(..) function which can copy the lists from one 0028 * version to the other. 0029 * 0030 * @warning Always follow these rules: 0031 * \li You must call initializeAppendedLists(bool) on construction, also in any copy-constructor, but before calling copyFrom(..). 0032 * \li The parameter to that function should be whether the lists in the items should be dynamic, and thus most times "true". 0033 * \li You must call freeAppendedLists() on destruction, our you will be leaking memory(only when dynamic) 0034 * 0035 * For each embedded list, you must use macros to define a global hash that will be used to allocate the temporary lists. 0036 * For example in @c identifier.cpp we have: 0037 * 0038 * @code 0039 * DEFINE_LIST_MEMBER_HASH(IdentifierPrivate, templateIdentifiers, uint); 0040 * @endcode 0041 * 0042 * In general, see @c identifier.cpp for an example on how to use these macros. 0043 * 0044 * @todo Document this a bit more 0045 * */ 0046 0047 enum { 0048 DynamicAppendedListMask = 1 << 31 0049 }; 0050 enum { 0051 DynamicAppendedListRevertMask = ~DynamicAppendedListMask 0052 }; 0053 /** 0054 * Manages a repository of items for temporary usage. The items will be allocated with an index on alloc(), 0055 * and freed on free(index). When freed, the same index will be re-used for a later allocation, thus no real allocations 0056 * will be happening in most cases. 0057 * The returned indices will always be ored with DynamicAppendedListMask. 0058 * 0059 */ 0060 template <class T, bool threadSafe = true> 0061 class TemporaryDataManager 0062 { 0063 public: 0064 explicit TemporaryDataManager(const QByteArray& id = {}) 0065 : m_id(id) 0066 { 0067 int first = alloc(); //Allocate the zero item, just to reserve that index 0068 Q_ASSERT(first == ( int )DynamicAppendedListMask); 0069 Q_UNUSED(first); 0070 } 0071 ~TemporaryDataManager() 0072 { 0073 free(DynamicAppendedListMask); //Free the zero index, so we don't get wrong warnings 0074 int cnt = usedItemCount(); 0075 if (cnt) //Don't use qDebug, because that may not work during destruction 0076 std::cout << m_id.constData() << " There were items left on destruction: " << usedItemCount() << "\n"; 0077 0078 qDeleteAll(m_items); 0079 } 0080 0081 inline T& item(int index) 0082 { 0083 //For performance reasons this function does not lock the mutex, it's called too often and must be 0084 //extremely fast. There is special measures in alloc() to make this safe. 0085 Q_ASSERT(index & DynamicAppendedListMask); 0086 0087 return *m_items.at(index & KDevelop::DynamicAppendedListRevertMask); 0088 } 0089 0090 ///Allocates an item index, which from now on you can get using item(), until you call free(..) on the index. 0091 ///The returned item is not initialized and may contain random older content, so you should clear it after getting it for the first time 0092 int alloc() 0093 { 0094 if (threadSafe) 0095 m_mutex.lock(); 0096 0097 int ret; 0098 if (!m_freeIndicesWithData.isEmpty()) { 0099 ret = m_freeIndicesWithData.pop(); 0100 } else if (!m_freeIndices.isEmpty()) { 0101 ret = m_freeIndices.pop(); 0102 Q_ASSERT(!m_items.at(ret)); 0103 m_items[ret] = new T; 0104 } else { 0105 if (m_items.size() >= m_items.capacity()) { 0106 //We need to re-allocate 0107 const int newItemsSize = m_items.capacity() + 20 + (m_items.capacity() / 3); 0108 const QVector<T*> oldItems = m_items; // backup 0109 m_items.reserve(newItemsSize); // detach, grow container 0110 0111 const auto now = time(nullptr); 0112 0113 // We do this in this place so it isn't called too often. The result is that we will always have some additional data around. 0114 // However the index itself should anyway not consume too much data. 0115 while (!m_deleteLater.isEmpty()) { 0116 // We delete only after 5 seconds 0117 if (now - m_deleteLater.first().first <= 5) { 0118 break; 0119 } 0120 m_deleteLater.removeFirst(); 0121 } 0122 0123 //The only function that does not lock the mutex is item(..), because that function must be very efficient. 0124 //Since it's only a few instructions from the moment m_items is read to the moment it's used, 0125 //deleting the old data after a few seconds should be safe. 0126 m_deleteLater.append(qMakePair(now, oldItems)); 0127 } 0128 0129 ret = m_items.size(); 0130 m_items.append(new T); 0131 Q_ASSERT(m_items.size() <= m_items.capacity()); 0132 } 0133 0134 if (threadSafe) 0135 m_mutex.unlock(); 0136 0137 Q_ASSERT(!(ret & DynamicAppendedListMask)); 0138 0139 return ret | DynamicAppendedListMask; 0140 } 0141 0142 void free(int index) 0143 { 0144 Q_ASSERT(index & DynamicAppendedListMask); 0145 index &= KDevelop::DynamicAppendedListRevertMask; 0146 0147 if (threadSafe) 0148 m_mutex.lock(); 0149 0150 freeItem(m_items.at(index)); 0151 0152 m_freeIndicesWithData.push(index); 0153 0154 //Hold the amount of free indices with data between 100 and 200 0155 if (m_freeIndicesWithData.size() > 200) { 0156 for (int a = 0; a < 100; ++a) { 0157 int deleteIndexData = m_freeIndicesWithData.pop(); 0158 auto& item = m_items[deleteIndexData]; 0159 delete item; 0160 item = nullptr; 0161 m_freeIndices.push(deleteIndexData); 0162 } 0163 } 0164 0165 if (threadSafe) 0166 m_mutex.unlock(); 0167 } 0168 0169 int usedItemCount() const 0170 { 0171 int ret = 0; 0172 for (auto* item : m_items) { 0173 if (item) { 0174 ++ret; 0175 } 0176 } 0177 0178 return ret - m_freeIndicesWithData.size(); 0179 } 0180 0181 private: 0182 //To save some memory, clear the lists 0183 void freeItem(T* item) 0184 { 0185 item->clear(); ///@todo make this a template specialization that only does this for containers 0186 } 0187 0188 Q_DISABLE_COPY(TemporaryDataManager) 0189 0190 private: 0191 QVector<T*> m_items; /// note: non-shared, ref count of 1 when accessed with non-const methods => no detach 0192 Stack<int> m_freeIndicesWithData; 0193 Stack<int> m_freeIndices; 0194 QMutex m_mutex; 0195 QByteArray m_id; 0196 QList<QPair<time_t, QVector<T*>>> m_deleteLater; 0197 }; 0198 0199 ///Foreach macro that takes a container and a function-name, and will iterate through the vector returned by that function, using the length returned by the function-name with "Size" appended. 0200 //This might be a little slow 0201 #define FOREACH_FUNCTION(item, container) \ 0202 for (uint a__ = 0, mustDo__ = 1, containerSize = container ## Size(); a__ < containerSize; ++a__) \ 0203 if ((mustDo__ == 0 || mustDo__ == 1) && (mustDo__ = 2)) \ 0204 for (item(container()[a__]); mustDo__; mustDo__ = 0) 0205 0206 #define DEFINE_LIST_MEMBER_HASH(container, member, type) \ 0207 using temporaryHash ## container ## member ## Type = KDevelop::TemporaryDataManager<KDevVarLengthArray<type, 10>>; \ 0208 Q_GLOBAL_STATIC_WITH_ARGS(temporaryHash ## container ## member ## Type, \ 0209 temporaryHash ## container ## member ## Static, ( # container "::" # member)) \ 0210 temporaryHash ## container ## member ## Type & temporaryHash ## container ## member() { \ 0211 return *temporaryHash ## container ## member ## Static; \ 0212 } 0213 0214 #define DECLARE_LIST_MEMBER_HASH(container, member, type) \ 0215 KDevelop::TemporaryDataManager<KDevVarLengthArray<type, 10>> &temporaryHash ## container ## member(); 0216 0217 ///This implements the interfaces so this container can be used as a predecessor for classes with appended lists. 0218 ///You should do this within the abstract base class that opens a tree of classes that can have appended lists, 0219 ///so each class that uses them, can also give its predecessor to START_APPENDE_LISTS, to increase flexibility. 0220 ///This creates a boolean entry that is initialized when initializeAppendedLists is called. 0221 ///You can call appendedListsDynamic() to find out whether the item is marked as dynamic. 0222 ///When this item is used, the same rules have to be followed as for a class with appended lists: You have to call 0223 ///initializeAppendedLists(...) and freeAppendedLists(..) 0224 ///Also, when you use this, you have to implement a uint classSize() function, that returns the size of the class including derived classes, 0225 ///but not including the dynamic data. Optionally you can implement a static bool appendedListDynamicDefault() function, that returns the default-value for the "dynamic" parameter. 0226 ///to initializeAppendedLists. 0227 #define APPENDED_LISTS_STUB(container) \ 0228 bool m_dynamic : 1; \ 0229 unsigned int offsetBehindLastList() const { return 0; } \ 0230 uint dynamicSize() const { return classSize(); } \ 0231 template <class T> bool listsEqual(const T& /*rhs*/) const { return true; } \ 0232 template <class T> void copyAllFrom(const T& /*rhs*/) const { } \ 0233 void initializeAppendedLists(bool dynamic = appendedListDynamicDefault()) { m_dynamic = dynamic; } \ 0234 void freeAppendedLists() { } \ 0235 bool appendedListsDynamic() const { return m_dynamic; } 0236 0237 ///use this if the class does not have a base class that also uses appended lists 0238 #define START_APPENDED_LISTS(container) \ 0239 unsigned int offsetBehindBase() const { return 0; } \ 0240 void freeDynamicData() { freeAppendedLists(); } 0241 0242 ///Use this if one of the base-classes of the container also has the appended lists interfaces implemented. 0243 ///To reduce the probability of future problems, you should give the direct base class this one inherits from. 0244 ///@note: Multiple inheritance is not supported, however it will work ok if only one of the base-classes uses appended lists. 0245 #define START_APPENDED_LISTS_BASE(container, base) \ 0246 unsigned int offsetBehindBase() const { return base :: offsetBehindLastList(); } \ 0247 void freeDynamicData() { freeAppendedLists(); base::freeDynamicData(); } 0248 0249 #define APPENDED_LIST_COMMON(container, type, name) \ 0250 uint name ## Data; \ 0251 unsigned int name ## Size() const { \ 0252 if ((name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) \ 0253 return 0; \ 0254 if (!appendedListsDynamic()) \ 0255 return name ## Data; \ 0256 return temporaryHash ## container ## name().item(name ## Data).size(); \ 0257 } \ 0258 KDevVarLengthArray<type, 10>& name ## List() { \ 0259 name ## NeedDynamicList(); \ 0260 return temporaryHash ## container ## name().item(name ## Data); \ 0261 } \ 0262 template <class T> bool name ## Equals(const T &rhs) const { \ 0263 unsigned int size = name ## Size(); \ 0264 if (size != rhs.name ## Size()) \ 0265 return false; \ 0266 for (uint a = 0; a < size; ++a) { \ 0267 if (!(name()[a] == rhs.name()[a])) \ 0268 return false; \ 0269 } \ 0270 return true; \ 0271 } \ 0272 template <class T> void name ## CopyFrom(const T &rhs) { \ 0273 if (rhs.name ## Size() == 0 && (name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) \ 0274 return; \ 0275 if (appendedListsDynamic()) { \ 0276 name ## NeedDynamicList(); \ 0277 KDevVarLengthArray<type, 10>& item(temporaryHash ## container ## name().item(name ## Data)); \ 0278 item.clear(); \ 0279 const type* otherCurr = rhs.name(); \ 0280 const type* otherEnd = otherCurr + rhs.name ## Size(); \ 0281 for (; otherCurr < otherEnd; ++otherCurr) \ 0282 item.append(*otherCurr); \ 0283 } else { \ 0284 Q_ASSERT(name ## Data == 0); /* It is dangerous to overwrite the contents of non-dynamic lists(Most probably a mistake) */ \ 0285 name ## Data = rhs.name ## Size(); \ 0286 auto* curr = const_cast<type*>(name()); \ 0287 type* end = curr + name ## Size(); \ 0288 const type* otherCurr = rhs.name(); \ 0289 for (; curr < end; ++curr, ++otherCurr) \ 0290 new (curr) type(*otherCurr); /* Call the copy constructors */ \ 0291 } \ 0292 } \ 0293 void name ## NeedDynamicList() { \ 0294 Q_ASSERT(appendedListsDynamic()); \ 0295 if ((name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) { \ 0296 name ## Data = temporaryHash ## container ## name().alloc(); \ 0297 Q_ASSERT(temporaryHash ## container ## name().item(name ## Data).isEmpty()); \ 0298 } \ 0299 } \ 0300 void name ## Initialize(bool dynamic) { name ## Data = (dynamic ? KDevelop::DynamicAppendedListMask : 0); } \ 0301 void name ## Free() { \ 0302 if (appendedListsDynamic()) { \ 0303 if (name ## Data & KDevelop::DynamicAppendedListRevertMask) \ 0304 temporaryHash ## container ## name().free(name ## Data); \ 0305 } else { \ 0306 auto* curr = const_cast<type*>(name()); \ 0307 type* end = curr + name ## Size(); \ 0308 for (; curr < end; ++curr) \ 0309 curr->~type(); /*call destructors*/ \ 0310 } \ 0311 } \ 0312 0313 0314 ///@todo Make these things a bit faster(less recursion) 0315 0316 #define APPENDED_LIST_FIRST(container, type, name) \ 0317 APPENDED_LIST_COMMON(container, type, name) \ 0318 const type * name() const { \ 0319 if ((name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) \ 0320 return nullptr; \ 0321 if (!appendedListsDynamic()) \ 0322 return reinterpret_cast<const type*>(reinterpret_cast<const char*>(this) + classSize() + \ 0323 offsetBehindBase()); \ 0324 else \ 0325 return temporaryHash ## container ## name().item(name ## Data).data(); \ 0326 } \ 0327 unsigned int name ## OffsetBehind() const { return name ## Size() * sizeof(type) + offsetBehindBase(); } \ 0328 template <class T> bool name ## ListChainEquals(const T &rhs) const { return name ## Equals(rhs); } \ 0329 template <class T> void name ## CopyAllFrom(const T &rhs) { name ## CopyFrom(rhs); } \ 0330 void name ## InitializeChain(bool dynamic) { name ## Initialize(dynamic); } \ 0331 void name ## FreeChain() { name ## Free(); } 0332 0333 #define APPENDED_LIST(container, type, name, predecessor) \ 0334 APPENDED_LIST_COMMON(container, type, name) \ 0335 const type * name() const { \ 0336 if ((name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) \ 0337 return nullptr; \ 0338 if (!appendedListsDynamic()) \ 0339 return reinterpret_cast<const type*>(reinterpret_cast<const char*>(this) + classSize() + \ 0340 predecessor ## OffsetBehind()); \ 0341 else \ 0342 return temporaryHash ## container ## name().item(name ## Data).data(); \ 0343 } \ 0344 unsigned int name ## OffsetBehind() const { return name ## Size() * sizeof(type) + predecessor ## OffsetBehind(); } \ 0345 template <class T> bool name ## ListChainEquals(const T &rhs) const { return name ## Equals(rhs) && \ 0346 predecessor ## ListChainEquals(rhs); } \ 0347 template <class T> void name ## CopyAllFrom(const T &rhs) { predecessor ## CopyAllFrom(rhs); name ## CopyFrom(rhs); \ 0348 } \ 0349 void name ## InitializeChain(bool dynamic) { name ## Initialize(dynamic); predecessor ## InitializeChain(dynamic); \ 0350 } \ 0351 void name ## FreeChain() { name ## Free(); predecessor ## FreeChain(); } 0352 0353 #define END_APPENDED_LISTS(container, predecessor) \ 0354 /* Returns the size of the object containing the appended lists, including them */ \ 0355 unsigned int completeSize() const { return classSize() + predecessor ## OffsetBehind(); } \ 0356 /* Compares all local appended lists(not from base classes) and returns true if they are equal */ \ 0357 template <class T> bool listsEqual(const T &rhs) const { return predecessor ## ListChainEquals(rhs); } \ 0358 /* Copies all the local appended lists(not from base classes) from the given item.*/ \ 0359 template <class T> void copyListsFrom(const T &rhs) { return predecessor ## CopyAllFrom(rhs); } \ 0360 void initializeAppendedLists(bool dynamic = appendedListDynamicDefault()) { \ 0361 predecessor ## Data = (dynamic ? KDevelop::DynamicAppendedListMask : 0); \ 0362 predecessor ## InitializeChain(dynamic); \ 0363 } \ 0364 void freeAppendedLists() { predecessor ## FreeChain(); } \ 0365 bool appendedListsDynamic() const { return predecessor ## Data & KDevelop::DynamicAppendedListMask; } \ 0366 unsigned int offsetBehindLastList() const { return predecessor ## OffsetBehind(); } \ 0367 uint dynamicSize() const { return offsetBehindLastList() + classSize(); } 0368 0369 /** 0370 * This is a class that allows you easily putting instances of your class into an ItemRepository as seen in itemrepository.h. 0371 * All your class needs to do is: 0372 * - Be implemented using the APPENDED_LIST macros. 0373 * - Have a real copy-constructor that additionally takes a "bool dynamic = true" parameter, which should be given to initializeAppendedLists 0374 * - Except for these appended lists, only contain directly copyable data like indices(no pointers, no virtual functions) 0375 * - Implement operator==(..) which should compare everything, including the lists. @warning The default operator will not work! 0376 * - Implement a hash() function. The hash should equal for two instances when operator==(..) returns true. 0377 * - Should be completely functional without a constructor called, only the data copied 0378 * - Implement a "bool persistent() const" function, that should check the reference-count or other information to decide whether the item should stay in the repository 0379 * If those conditions are fulfilled, the data can easily be put into a repository using this request class. 0380 * */ 0381 0382 template <class Type, uint averageAppendedBytes = 8> 0383 class AppendedListItemRequest 0384 { 0385 public: 0386 AppendedListItemRequest(const Type& item) : m_item(item) 0387 { 0388 } 0389 0390 enum { 0391 AverageSize = sizeof(Type) + averageAppendedBytes 0392 }; 0393 0394 unsigned int hash() const 0395 { 0396 return m_item.hash(); 0397 } 0398 0399 uint itemSize() const 0400 { 0401 return m_item.dynamicSize(); 0402 } 0403 0404 void createItem(Type* item) const 0405 { 0406 new (item) Type(m_item, false); 0407 } 0408 0409 static void destroy(Type* item, KDevelop::AbstractItemRepository&) 0410 { 0411 item->~Type(); 0412 } 0413 0414 static bool persistent(const Type* item) 0415 { 0416 return item->persistent(); 0417 } 0418 0419 bool equals(const Type* item) const 0420 { 0421 return m_item == *item; 0422 } 0423 0424 const Type& m_item; 0425 }; 0426 } 0427 0428 ///This function is outside of the namespace, so it can always be found. It's used as default-parameter to initializeAppendedLists(..), 0429 ///and you can for example implement a function called like this in your local class hierarchy to override this default. 0430 inline bool appendedListDynamicDefault() 0431 { 0432 return true; 0433 } 0434 0435 #endif