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