/* * Copyright (C) 2014 Pavel Kirienko */ #ifndef UAVCAN_UTIL_MULTISET_HPP_INCLUDED #define UAVCAN_UTIL_MULTISET_HPP_INCLUDED #include #include #include #include #include #include #include #if !defined(UAVCAN_CPP_VERSION) || !defined(UAVCAN_CPP11) # error UAVCAN_CPP_VERSION #endif namespace uavcan { /** * Slow but memory efficient unordered multiset. Unlike Map<>, this container does not move objects, so * they don't have to be copyable. * * Items can be allocated in a static buffer or in the node's memory pool if the static buffer is exhausted. * * Number of static entries must not be less than 1. */ template class UAVCAN_EXPORT Multiset : Noncopyable { struct Item : ::uavcan::Noncopyable { T* ptr; #if UAVCAN_CPP_VERSION >= UAVCAN_CPP11 alignas(T) unsigned char pool[sizeof(T)]; ///< Memory efficient version #else union { unsigned char pool[sizeof(T)]; /* * Such alignment does not guarantee safety for all types (only for libuavcan internal ones); * however, increasing it is too memory inefficient. So it is recommended to use C++11, where * this issue is resolved with alignas() (see above). */ void* _aligner1_; long long _aligner2_; }; #endif Item() : ptr(NULL) { fill_n(pool, sizeof(pool), static_cast(0)); } ~Item() { destroy(); } bool isConstructed() const { return ptr != NULL; } void destroy() { if (ptr != NULL) { ptr->~T(); ptr = NULL; fill_n(pool, sizeof(pool), static_cast(0)); } } }; private: struct Chunk : LinkedListNode, ::uavcan::Noncopyable { enum { NumItems = (MemPoolBlockSize - sizeof(LinkedListNode)) / sizeof(Item) }; Item items[NumItems]; Chunk() { StaticAssert<(static_cast(NumItems) > 0)>::check(); IsDynamicallyAllocatable::check(); UAVCAN_ASSERT(!items[0].isConstructed()); } static Chunk* instantiate(IPoolAllocator& allocator) { void* const praw = allocator.allocate(sizeof(Chunk)); if (praw == NULL) { return NULL; } return new (praw) Chunk(); } static void destroy(Chunk*& obj, IPoolAllocator& allocator) { if (obj != NULL) { obj->~Chunk(); allocator.deallocate(obj); obj = NULL; } } Item* findFreeSlot() { for (unsigned i = 0; i < static_cast(NumItems); i++) { if (!items[i].isConstructed()) { return items + i; } } return NULL; } }; /* * Data */ LinkedListRoot list_; IPoolAllocator& allocator_; Item static_[NumStaticEntries]; /* * Methods */ Item* findOrCreateFreeSlot(); void compact(); enum RemoveStrategy { RemoveOne, RemoveAll }; template void removeWhere(Predicate predicate, RemoveStrategy strategy); struct YesPredicate { bool operator()(const T&) const { return true; } }; struct IndexPredicate : ::uavcan::Noncopyable { unsigned index; IndexPredicate(unsigned target_index) : index(target_index) { } bool operator()(const T&) { return index-- == 0; } }; struct ComparingPredicate { const T& reference; ComparingPredicate(const T& ref) : reference(ref) { } bool operator()(const T& sample) { return reference == sample; } }; template struct OperatorToFalsePredicateAdapter : ::uavcan::Noncopyable { Operator oper; OperatorToFalsePredicateAdapter(Operator o) : oper(o) { } bool operator()(T& item) { oper(item); return false; } bool operator()(const T& item) const { oper(item); return false; } }; public: Multiset(IPoolAllocator& allocator) : allocator_(allocator) { } ~Multiset() { clear(); } /** * Creates one item in-place and returns a pointer to it. * If creation fails due to lack of memory, NULL will be returned. * Complexity is O(N). */ T* emplace() { Item* const item = findOrCreateFreeSlot(); if (item == NULL) { return NULL; } UAVCAN_ASSERT(item->ptr == NULL); item->ptr = new (item->pool) T(); return item->ptr; } template T* emplace(P1 p1) { Item* const item = findOrCreateFreeSlot(); if (item == NULL) { return NULL; } UAVCAN_ASSERT(item->ptr == NULL); item->ptr = new (item->pool) T(p1); return item->ptr; } template T* emplace(P1 p1, P2 p2) { Item* const item = findOrCreateFreeSlot(); if (item == NULL) { return NULL; } UAVCAN_ASSERT(item->ptr == NULL); item->ptr = new (item->pool) T(p1, p2); return item->ptr; } template T* emplace(P1 p1, P2 p2, P3 p3) { Item* const item = findOrCreateFreeSlot(); if (item == NULL) { return NULL; } UAVCAN_ASSERT(item->ptr == NULL); item->ptr = new (item->pool) T(p1, p2, p3); return item->ptr; } /** * Removes entries where the predicate returns true. * Predicate prototype: * bool (T& item) */ template void removeAllWhere(Predicate predicate) { removeWhere(predicate, RemoveAll); } template void removeFirstWhere(Predicate predicate) { removeWhere(predicate, RemoveOne); } void removeFirst(const T& ref) { removeFirstWhere(ComparingPredicate(ref)); } void removeAll(const T& ref) { removeAllWhere(ComparingPredicate(ref)); } void clear() { removeAllWhere(YesPredicate()); } /** * Returns first entry where the predicate returns true. * Predicate prototype: * bool (const T& item) */ template T* find(Predicate predicate); template const T* find(Predicate predicate) const { return const_cast(this)->find(predicate); } /** * Calls Operator for each item of the set. * Operator prototype: * void (T& item) * void (const T& item) - const overload */ template void forEach(Operator oper) { OperatorToFalsePredicateAdapter adapter(oper); (void)find&>(adapter); } template void forEach(Operator oper) const { const OperatorToFalsePredicateAdapter adapter(oper); (void)find&>(adapter); } /** * Returns an item located at the specified position from the beginning. * Note that addition and removal operations invalidate indices. * If index is greater than or equal the number of items, null pointer will be returned. * Complexity is O(N). */ T* getByIndex(unsigned index) { IndexPredicate predicate(index); return find(predicate); } const T* getByIndex(unsigned index) const { return const_cast(this)->getByIndex(index); } /** * Complexity is O(1). */ bool isEmpty() const { return find(YesPredicate()) == NULL; } /** * Counts number of items stored. * Best case complexity is O(N). */ unsigned getSize() const { return getNumStaticItems() + getNumDynamicItems(); } /** * For testing, do not use directly. */ unsigned getNumStaticItems() const; unsigned getNumDynamicItems() const; }; // ---------------------------------------------------------------------------- /* * Multiset<> */ template typename Multiset::Item* Multiset::findOrCreateFreeSlot() { #if !UAVCAN_TINY // Search in static pool for (unsigned i = 0; i < NumStaticEntries; i++) { if (!static_[i].isConstructed()) { return &static_[i]; } } #endif // Search in dynamic pool { Chunk* p = list_.get(); while (p) { Item* const dyn = p->findFreeSlot(); if (dyn != NULL) { return dyn; } p = p->getNextListNode(); } } // Create new dynamic chunk Chunk* const chunk = Chunk::instantiate(allocator_); if (chunk == NULL) { return NULL; } list_.insert(chunk); return &chunk->items[0]; } template void Multiset::compact() { Chunk* p = list_.get(); while (p) { Chunk* const next = p->getNextListNode(); bool remove_this = true; for (int i = 0; i < Chunk::NumItems; i++) { if (p->items[i].isConstructed()) { remove_this = false; break; } } if (remove_this) { list_.remove(p); Chunk::destroy(p, allocator_); } p = next; } } template template void Multiset::removeWhere(Predicate predicate, const RemoveStrategy strategy) { unsigned num_removed = 0; #if !UAVCAN_TINY for (unsigned i = 0; i < NumStaticEntries; i++) { if (static_[i].isConstructed()) { if (predicate(*static_[i].ptr)) { num_removed++; static_[i].destroy(); if (strategy == RemoveOne) { break; } } } } #endif Chunk* p = list_.get(); while (p != NULL) { Chunk* const next_chunk = p->getNextListNode(); // For the case if the current entry gets modified if ((num_removed > 0) && (strategy == RemoveOne)) { break; } for (int i = 0; i < Chunk::NumItems; i++) { Item& item = p->items[i]; if (item.isConstructed()) { if (predicate(*item.ptr)) { num_removed++; item.destroy(); if (strategy == RemoveOne) { break; } } } } p = next_chunk; } if (num_removed > 0) { compact(); } } template template T* Multiset::find(Predicate predicate) { #if !UAVCAN_TINY for (unsigned i = 0; i < NumStaticEntries; i++) { if (static_[i].isConstructed()) { if (predicate(*static_[i].ptr)) { return static_[i].ptr; } } } #endif Chunk* p = list_.get(); while (p != NULL) { Chunk* const next_chunk = p->getNextListNode(); // For the case if the current entry gets modified for (int i = 0; i < Chunk::NumItems; i++) { if (p->items[i].isConstructed()) { if (predicate(*p->items[i].ptr)) { return p->items[i].ptr; } } } p = next_chunk; } return NULL; } template unsigned Multiset::getNumStaticItems() const { unsigned num = 0; #if !UAVCAN_TINY for (unsigned i = 0; i < NumStaticEntries; i++) { num += static_[i].isConstructed() ? 1U : 0U; } #endif return num; } template unsigned Multiset::getNumDynamicItems() const { unsigned num = 0; Chunk* p = list_.get(); while (p) { for (int i = 0; i < Chunk::NumItems; i++) { num += p->items[i].isConstructed() ? 1U : 0U; } p = p->getNextListNode(); } return num; } } #endif // Include guard