/* * Copyright (C) 2014 Pavel Kirienko */ #pragma once #include #include #include #include #include #include namespace uavcan { /** * Slow but memory efficient KV container. * Type requirements: * Both key and value must be copyable, assignable and default constructible. * Key must implement a comparison operator. * Key's default constructor must initialize the object into invalid state. * Size of Key + Value + padding must not exceed MemPoolBlockSize. */ template class UAVCAN_EXPORT MapBase : Noncopyable { template friend class Map; UAVCAN_PACKED_BEGIN struct KVPair { Key key; Value value; KVPair() { } KVPair(const Key& arg_key, const Value& arg_value) : key(arg_key) , value(arg_value) { } bool match(const Key& rhs) const { return rhs == key; } }; struct KVGroup : LinkedListNode { enum { NumKV = (MemPoolBlockSize - sizeof(LinkedListNode)) / sizeof(KVPair) }; KVPair kvs[NumKV]; KVGroup() { StaticAssert<(static_cast(NumKV) > 0)>::check(); IsDynamicallyAllocatable::check(); } static KVGroup* instantiate(IPoolAllocator& allocator) { void* const praw = allocator.allocate(sizeof(KVGroup)); if (praw == NULL) { return NULL; } return new (praw) KVGroup(); } static void destroy(KVGroup*& obj, IPoolAllocator& allocator) { if (obj != NULL) { obj->~KVGroup(); allocator.deallocate(obj); obj = NULL; } } KVPair* find(const Key& key) { for (unsigned i = 0; i < static_cast(NumKV); i++) { if (kvs[i].match(key)) { return kvs + i; } } return NULL; } }; UAVCAN_PACKED_END LinkedListRoot list_; IPoolAllocator& allocator_; #if !UAVCAN_TINY KVPair* const static_; const unsigned num_static_entries_; #endif KVPair* find(const Key& key); #if !UAVCAN_TINY void optimizeStorage(); #endif void compact(); struct YesPredicate { bool operator()(const Key& k, const Value& v) const { (void)k; (void)v; return true; } }; protected: #if UAVCAN_TINY MapBase(IPoolAllocator& allocator) : allocator_(allocator) { assert(Key() == Key()); } #else MapBase(KVPair* static_buf, unsigned num_static_entries, IPoolAllocator& allocator) : allocator_(allocator) , static_(static_buf) , num_static_entries_(num_static_entries) { assert(Key() == Key()); } #endif /// Derived class destructor must call removeAll(); ~MapBase() { } public: Value* access(const Key& key); /// If entry with the same key already exists, it will be replaced Value* insert(const Key& key, const Value& value); void remove(const Key& key); /** * Remove entries where predicate returns true. * Predicate prototype: * bool (const Key& key, const Value& value) */ template void removeWhere(Predicate predicate); template const Key* findFirstKey(Predicate predicate) const; void removeAll(); bool isEmpty() const; /// For testing unsigned getNumStaticPairs() const; /// For testing unsigned getNumDynamicPairs() const; }; template class UAVCAN_EXPORT Map : public MapBase { typename MapBase::KVPair static_[NumStaticEntries]; public: #if !UAVCAN_TINY // This instantiation will not be valid in UAVCAN_TINY mode explicit Map(IPoolAllocator& allocator) : MapBase(static_, NumStaticEntries, allocator) { } ~Map() { this->removeAll(); } #endif // !UAVCAN_TINY }; template class UAVCAN_EXPORT Map : public MapBase { public: explicit Map(IPoolAllocator& allocator) #if UAVCAN_TINY : MapBase(allocator) #else : MapBase(NULL, 0, allocator) #endif { } ~Map() { this->removeAll(); } }; // ---------------------------------------------------------------------------- /* * MapBase<> */ template typename MapBase::KVPair* MapBase::find(const Key& key) { #if !UAVCAN_TINY for (unsigned i = 0; i < num_static_entries_; i++) { if (static_[i].match(key)) { return static_ + i; } } #endif KVGroup* p = list_.get(); while (p) { KVPair* const kv = p->find(key); if (kv) { return kv; } p = p->getNextListNode(); } return NULL; } #if !UAVCAN_TINY template void MapBase::optimizeStorage() { while (true) { // Looking for first EMPTY static entry KVPair* stat = NULL; for (unsigned i = 0; i < num_static_entries_; i++) { if (static_[i].match(Key())) { stat = static_ + i; break; } } if (stat == NULL) { break; } // Looking for the first NON-EMPTY dynamic entry, erasing immediately KVGroup* p = list_.get(); KVPair dyn; while (p) { bool stop = false; for (int i = 0; i < KVGroup::NumKV; i++) { if (!p->kvs[i].match(Key())) // Non empty { dyn = p->kvs[i]; // Copy by value p->kvs[i] = KVPair(); // Erase immediately stop = true; break; } } if (stop) { break; } p = p->getNextListNode(); } if (dyn.match(Key())) { break; } // Migrating *stat = dyn; } } #endif // !UAVCAN_TINY template void MapBase::compact() { KVGroup* p = list_.get(); while (p) { KVGroup* const next = p->getNextListNode(); bool remove_this = true; for (int i = 0; i < KVGroup::NumKV; i++) { if (!p->kvs[i].match(Key())) { remove_this = false; break; } } if (remove_this) { list_.remove(p); KVGroup::destroy(p, allocator_); } p = next; } } template Value* MapBase::access(const Key& key) { assert(!(key == Key())); KVPair* const kv = find(key); return kv ? &kv->value : NULL; } template Value* MapBase::insert(const Key& key, const Value& value) { assert(!(key == Key())); remove(key); KVPair* const kv = find(Key()); if (kv) { *kv = KVPair(key, value); return &kv->value; } KVGroup* const kvg = KVGroup::instantiate(allocator_); if (kvg == NULL) { return NULL; } list_.insert(kvg); kvg->kvs[0] = KVPair(key, value); return &kvg->kvs[0].value; } template void MapBase::remove(const Key& key) { assert(!(key == Key())); KVPair* const kv = find(key); if (kv) { *kv = KVPair(); #if !UAVCAN_TINY optimizeStorage(); #endif compact(); } } template template void MapBase::removeWhere(Predicate predicate) { unsigned num_removed = 0; #if !UAVCAN_TINY for (unsigned i = 0; i < num_static_entries_; i++) { if (!static_[i].match(Key())) { if (predicate(static_[i].key, static_[i].value)) { num_removed++; static_[i] = KVPair(); } } } #endif KVGroup* p = list_.get(); while (p) { for (int i = 0; i < KVGroup::NumKV; i++) { const KVPair* const kv = p->kvs + i; if (!kv->match(Key())) { if (predicate(kv->key, kv->value)) { num_removed++; p->kvs[i] = KVPair(); } } } p = p->getNextListNode(); } if (num_removed > 0) { #if !UAVCAN_TINY optimizeStorage(); #endif compact(); } } template template const Key* MapBase::findFirstKey(Predicate predicate) const { #if !UAVCAN_TINY for (unsigned i = 0; i < num_static_entries_; i++) { if (!static_[i].match(Key())) { if (predicate(static_[i].key, static_[i].value)) { return &static_[i].key; } } } #endif KVGroup* p = list_.get(); while (p) { for (int i = 0; i < KVGroup::NumKV; i++) { const KVPair* const kv = p->kvs + i; if (!kv->match(Key())) { if (predicate(kv->key, kv->value)) { return &p->kvs[i].key; } } } p = p->getNextListNode(); } return NULL; } template void MapBase::removeAll() { removeWhere(YesPredicate()); } template bool MapBase::isEmpty() const { return (getNumStaticPairs() == 0) && (getNumDynamicPairs() == 0); } template unsigned MapBase::getNumStaticPairs() const { unsigned num = 0; #if !UAVCAN_TINY for (unsigned i = 0; i < num_static_entries_; i++) { if (!static_[i].match(Key())) { num++; } } #endif return num; } template unsigned MapBase::getNumDynamicPairs() const { unsigned num = 0; KVGroup* p = list_.get(); while (p) { for (int i = 0; i < KVGroup::NumKV; i++) { const KVPair* const kv = p->kvs + i; if (!kv->match(Key())) { num++; } } p = p->getNextListNode(); } return num; } }