/* * Copyright (C) 2014 Pavel Kirienko */ #ifndef UAVCAN_UTIL_MAP_HPP_INCLUDED #define UAVCAN_UTIL_MAP_HPP_INCLUDED #include #include #include #include #include #include #include namespace uavcan { /** * Slow but memory efficient KV container. * * KV pairs will be allocated in the node's memory pool. * * Please be aware that this container does not perform any speed optimizations to minimize memory footprint, * so the complexity of most operations is O(N). * * 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 Map : Noncopyable { public: struct KVPair { Value value; // Key and value are swapped because this may allow to reduce padding (depending on types) Key key; KVPair() : value(), key() { } KVPair(const Key& arg_key, const Value& arg_value) : value(arg_value), key(arg_key) { } bool match(const Key& rhs) const { return rhs == key; } }; private: 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 == UAVCAN_NULLPTR) { return UAVCAN_NULLPTR; } return new (praw) KVGroup(); } static void destroy(KVGroup*& obj, IPoolAllocator& allocator) { if (obj != UAVCAN_NULLPTR) { obj->~KVGroup(); allocator.deallocate(obj); obj = UAVCAN_NULLPTR; } } KVPair* find(const Key& key) { for (unsigned i = 0; i < static_cast(NumKV); i++) { if (kvs[i].match(key)) { return kvs + i; } } return UAVCAN_NULLPTR; } }; LinkedListRoot list_; IPoolAllocator& allocator_; KVPair* findKey(const Key& key); void compact(); struct YesPredicate { bool operator()(const Key&, const Value&) const { return true; } }; public: Map(IPoolAllocator& allocator) : allocator_(allocator) { UAVCAN_ASSERT(Key() == Key()); } ~Map() { clear(); } /** * Returns null pointer if there's no such entry. */ 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); /** * Does nothing if there's no such entry. */ void remove(const Key& key); /** * Removes entries where the predicate returns true. * Predicate prototype: * bool (Key& key, Value& value) */ template void removeAllWhere(Predicate predicate); /** * Returns first entry where the predicate returns true. * Predicate prototype: * bool (const Key& key, const Value& value) */ template const Key* find(Predicate predicate) const; /** * Removes all items. */ void clear(); /** * Returns a key-value pair located at the specified position from the beginning. * Note that any insertion or deletion may greatly disturb internal ordering, so use with care. * If index is greater than or equal the number of pairs, null pointer will be returned. */ KVPair* getByIndex(unsigned index); const KVPair* getByIndex(unsigned index) const; /** * Complexity is O(1). */ bool isEmpty() const { return find(YesPredicate()) == UAVCAN_NULLPTR; } /** * Complexity is O(N). */ unsigned getSize() const; }; // ---------------------------------------------------------------------------- /* * Map<> */ template typename Map::KVPair* Map::findKey(const Key& key) { KVGroup* p = list_.get(); while (p) { KVPair* const kv = p->find(key); if (kv) { return kv; } p = p->getNextListNode(); } return UAVCAN_NULLPTR; } template void Map::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* Map::access(const Key& key) { UAVCAN_ASSERT(!(key == Key())); KVPair* const kv = findKey(key); return kv ? &kv->value : UAVCAN_NULLPTR; } template Value* Map::insert(const Key& key, const Value& value) { UAVCAN_ASSERT(!(key == Key())); remove(key); KVPair* const kv = findKey(Key()); if (kv) { *kv = KVPair(key, value); return &kv->value; } KVGroup* const kvg = KVGroup::instantiate(allocator_); if (kvg == UAVCAN_NULLPTR) { return UAVCAN_NULLPTR; } list_.insert(kvg); kvg->kvs[0] = KVPair(key, value); return &kvg->kvs[0].value; } template void Map::remove(const Key& key) { UAVCAN_ASSERT(!(key == Key())); KVPair* const kv = findKey(key); if (kv) { *kv = KVPair(); compact(); } } template template void Map::removeAllWhere(Predicate predicate) { unsigned num_removed = 0; KVGroup* p = list_.get(); while (p != UAVCAN_NULLPTR) { KVGroup* const next_group = p->getNextListNode(); 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 = next_group; } if (num_removed > 0) { compact(); } } template template const Key* Map::find(Predicate predicate) const { KVGroup* p = list_.get(); while (p != UAVCAN_NULLPTR) { KVGroup* const next_group = p->getNextListNode(); 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 = next_group; } return UAVCAN_NULLPTR; } template void Map::clear() { removeAllWhere(YesPredicate()); } template typename Map::KVPair* Map::getByIndex(unsigned index) { // Slowly crawling through the dynamic storage KVGroup* p = list_.get(); while (p != UAVCAN_NULLPTR) { KVGroup* const next_group = p->getNextListNode(); for (int i = 0; i < KVGroup::NumKV; i++) { KVPair* const kv = p->kvs + i; if (!kv->match(Key())) { if (index == 0) { return kv; } index--; } } p = next_group; } return UAVCAN_NULLPTR; } template const typename Map::KVPair* Map::getByIndex(unsigned index) const { return const_cast*>(this)->getByIndex(index); } template unsigned Map::getSize() 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; } } #endif // UAVCAN_UTIL_MAP_HPP_INCLUDED