mirror of
https://gitee.com/mirrors_PX4/PX4-Autopilot.git
synced 2026-10-23 17:23:33 +08:00
KV container for TransferID
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <stdint.h>
|
||||
#include <uavcan/internal/linked_list.hpp>
|
||||
#include <uavcan/internal/impl_constants.hpp>
|
||||
#include <uavcan/internal/dynamic_memory.hpp>
|
||||
|
||||
namespace uavcan
|
||||
{
|
||||
/**
|
||||
* Slow but memory efficient KV container.
|
||||
* Type requirements:
|
||||
* Both key and value must be copyable 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 MEM_POOL_BLOCK_SIZE.
|
||||
*/
|
||||
template <typename Key, typename Value, unsigned int NUM_STATIC_ENTRIES = 1>
|
||||
class Map
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
struct KVPair
|
||||
{
|
||||
Key key;
|
||||
Value value;
|
||||
KVPair() { }
|
||||
KVPair(const Key& key, const Value& value) : key(key), value(value) { }
|
||||
bool match(const Key& rhs) const { return rhs == key; }
|
||||
};
|
||||
|
||||
struct KVGroup : LinkedListNode<KVGroup>
|
||||
{
|
||||
enum { NUM_KV = (MEM_POOL_BLOCK_SIZE - sizeof(LinkedListNode<KVGroup>)) / sizeof(KVPair) };
|
||||
KVPair kvs[NUM_KV];
|
||||
|
||||
KVGroup()
|
||||
{
|
||||
StaticAssert<(NUM_KV > 0)>::check();
|
||||
IsDynamicallyAllocatable<KVGroup>::check();
|
||||
}
|
||||
|
||||
KVPair* find(const Key& key)
|
||||
{
|
||||
for (int i = 0; i < NUM_KV; i++)
|
||||
if (kvs[i].match(key))
|
||||
return kvs + i;
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
LinkedListRoot<KVGroup> list_;
|
||||
IAllocator* const allocator_;
|
||||
KVPair static_[NUM_STATIC_ENTRIES];
|
||||
|
||||
KVPair* find(const Key& key)
|
||||
{
|
||||
for (unsigned int i = 0; i < NUM_STATIC_ENTRIES; i++)
|
||||
if (static_[i].match(key))
|
||||
return static_ + i;
|
||||
|
||||
KVGroup* p = list_.get();
|
||||
while (p)
|
||||
{
|
||||
KVPair* const kv = p->find(key);
|
||||
if (kv)
|
||||
return kv;
|
||||
p = p->getNextListNode();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void optimizeStorage()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// Looking for first EMPTY static entry
|
||||
KVPair* stat = NULL;
|
||||
for (unsigned int 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::NUM_KV; 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;
|
||||
}
|
||||
}
|
||||
|
||||
void compact()
|
||||
{
|
||||
KVGroup* p = list_.get();
|
||||
while (p)
|
||||
{
|
||||
KVGroup* const next = p->getNextListNode();
|
||||
bool remove_this = true;
|
||||
for (int i = 0; i < KVGroup::NUM_KV; i++)
|
||||
{
|
||||
if (!p->kvs[i].match(Key()))
|
||||
{
|
||||
remove_this = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (remove_this)
|
||||
{
|
||||
list_.remove(p);
|
||||
p->~KVGroup();
|
||||
allocator_->deallocate(p);
|
||||
}
|
||||
p = next;
|
||||
}
|
||||
}
|
||||
|
||||
struct YesPredicate
|
||||
{
|
||||
bool operator()(const Key& k, const Value& v) const { (void)k; (void)v; return true; }
|
||||
};
|
||||
|
||||
// This container is not copyable
|
||||
Map(const Map&);
|
||||
bool operator=(const Map&);
|
||||
|
||||
public:
|
||||
Map(IAllocator* allocator)
|
||||
: allocator_(allocator)
|
||||
{
|
||||
assert(allocator);
|
||||
assert(Key() == Key());
|
||||
}
|
||||
|
||||
~Map() { removeAll(); }
|
||||
|
||||
Value* access(const Key& key)
|
||||
{
|
||||
assert(!(key == Key()));
|
||||
KVPair* const kv = find(key);
|
||||
return kv ? &kv->value : NULL;
|
||||
}
|
||||
|
||||
/// If entry with the same key already exists, it will be replaced
|
||||
bool insert(const Key& key, const Value& value)
|
||||
{
|
||||
assert(!(key == Key()));
|
||||
remove(key);
|
||||
|
||||
KVPair* const kv = find(Key());
|
||||
if (kv)
|
||||
{
|
||||
*kv = KVPair(key, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
void* const praw = allocator_->allocate(sizeof(KVGroup));
|
||||
if (praw == NULL)
|
||||
return false;
|
||||
|
||||
KVGroup* const kvg = new (praw) KVGroup();
|
||||
assert(kvg);
|
||||
kvg->kvs[0] = KVPair(key, value);
|
||||
list_.insert(kvg);
|
||||
return true;
|
||||
}
|
||||
|
||||
void remove(const Key& key)
|
||||
{
|
||||
assert(!(key == Key()));
|
||||
KVPair* const kv = find(key);
|
||||
if (kv)
|
||||
{
|
||||
*kv = KVPair();
|
||||
optimizeStorage();
|
||||
compact();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove entries where predicate returns true.
|
||||
* Predicate prototype:
|
||||
* bool (const Key& key, const Value& value)
|
||||
*/
|
||||
template <typename Predicate>
|
||||
void removeWhere(Predicate predicate)
|
||||
{
|
||||
unsigned int num_removed = 0;
|
||||
|
||||
for (unsigned int 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KVGroup* p = list_.get();
|
||||
while (p)
|
||||
{
|
||||
for (int i = 0; i < KVGroup::NUM_KV; 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)
|
||||
{
|
||||
optimizeStorage();
|
||||
compact();
|
||||
}
|
||||
}
|
||||
|
||||
void removeAll()
|
||||
{
|
||||
removeWhere(YesPredicate());
|
||||
}
|
||||
|
||||
/// For testing
|
||||
unsigned int getNumStaticPairs() const
|
||||
{
|
||||
unsigned int num = 0;
|
||||
for (unsigned int i = 0; i < NUM_STATIC_ENTRIES; i++)
|
||||
if (!static_[i].match(Key()))
|
||||
num++;
|
||||
return num;
|
||||
}
|
||||
|
||||
/// For testing
|
||||
unsigned int getNumDynamicPairs() const
|
||||
{
|
||||
unsigned int num = 0;
|
||||
KVGroup* p = list_.get();
|
||||
while (p)
|
||||
{
|
||||
for (int i = 0; i < KVGroup::NUM_KV; i++)
|
||||
{
|
||||
const KVPair* const kv = p->kvs + i;
|
||||
if (!kv->match(Key()))
|
||||
num++;
|
||||
}
|
||||
p = p->getNextListNode();
|
||||
}
|
||||
return num;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <gtest/gtest.h>
|
||||
#include <uavcan/internal/map.hpp>
|
||||
|
||||
|
||||
static std::string toString(long x)
|
||||
{
|
||||
char buf[80];
|
||||
std::snprintf(buf, sizeof(buf), "%li", x);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
static bool oddValuePredicate(const std::string& key, const std::string& value)
|
||||
{
|
||||
EXPECT_FALSE(key.empty());
|
||||
EXPECT_FALSE(value.empty());
|
||||
const int num = atoi(value.c_str());
|
||||
return num & 1;
|
||||
}
|
||||
|
||||
|
||||
TEST(Map, Basic)
|
||||
{
|
||||
using uavcan::Map;
|
||||
|
||||
static const int POOL_BLOCKS = 3;
|
||||
uavcan::PoolAllocator<uavcan::MEM_POOL_BLOCK_SIZE * POOL_BLOCKS, uavcan::MEM_POOL_BLOCK_SIZE> pool;
|
||||
uavcan::PoolManager<2> poolmgr;
|
||||
poolmgr.addPool(&pool);
|
||||
|
||||
typedef Map<std::string, std::string, 2> MapType;
|
||||
std::auto_ptr<MapType> map(new MapType(&poolmgr));
|
||||
|
||||
// Empty
|
||||
ASSERT_FALSE(map->access("hi"));
|
||||
map->remove("foo");
|
||||
ASSERT_EQ(0, pool.getNumUsedBlocks());
|
||||
|
||||
// Static insertion
|
||||
ASSERT_TRUE(map->insert("1", "a"));
|
||||
ASSERT_TRUE(map->insert("2", "b"));
|
||||
ASSERT_EQ(0, pool.getNumUsedBlocks());
|
||||
ASSERT_EQ(2, map->getNumStaticPairs());
|
||||
ASSERT_EQ(0, map->getNumDynamicPairs());
|
||||
|
||||
// Dynamic insertion
|
||||
ASSERT_TRUE(map->insert("3", "c"));
|
||||
ASSERT_EQ(1, pool.getNumUsedBlocks());
|
||||
|
||||
ASSERT_TRUE(map->insert("4", "d"));
|
||||
ASSERT_EQ(1, pool.getNumUsedBlocks()); // Assuming that at least 2 items fit one block
|
||||
ASSERT_EQ(2, map->getNumStaticPairs());
|
||||
ASSERT_EQ(2, map->getNumDynamicPairs());
|
||||
|
||||
// Making sure everything is here
|
||||
ASSERT_EQ("a", *map->access("1"));
|
||||
ASSERT_EQ("b", *map->access("2"));
|
||||
ASSERT_EQ("c", *map->access("3"));
|
||||
ASSERT_EQ("d", *map->access("4"));
|
||||
ASSERT_FALSE(map->access("hi"));
|
||||
|
||||
// Modifying existing entries
|
||||
*map->access("1") = "A";
|
||||
*map->access("2") = "B";
|
||||
*map->access("3") = "C";
|
||||
*map->access("4") = "D";
|
||||
ASSERT_EQ("A", *map->access("1"));
|
||||
ASSERT_EQ("B", *map->access("2"));
|
||||
ASSERT_EQ("C", *map->access("3"));
|
||||
ASSERT_EQ("D", *map->access("4"));
|
||||
|
||||
// Removing one static
|
||||
map->remove("1"); // One of dynamics now migrates to the static storage
|
||||
map->remove("foo"); // There's no such thing anyway
|
||||
ASSERT_EQ(1, pool.getNumUsedBlocks());
|
||||
ASSERT_EQ(2, map->getNumStaticPairs());
|
||||
ASSERT_EQ(1, map->getNumDynamicPairs());
|
||||
|
||||
ASSERT_FALSE(map->access("1"));
|
||||
ASSERT_EQ("B", *map->access("2"));
|
||||
ASSERT_EQ("C", *map->access("3"));
|
||||
ASSERT_EQ("D", *map->access("4"));
|
||||
|
||||
// Removing another static
|
||||
map->remove("2");
|
||||
ASSERT_EQ(2, map->getNumStaticPairs());
|
||||
ASSERT_EQ(0, map->getNumDynamicPairs());
|
||||
ASSERT_EQ(0, pool.getNumUsedBlocks()); // No dynamic entries left
|
||||
|
||||
ASSERT_FALSE(map->access("1"));
|
||||
ASSERT_FALSE(map->access("2"));
|
||||
ASSERT_EQ("C", *map->access("3"));
|
||||
ASSERT_EQ("D", *map->access("4"));
|
||||
|
||||
// Adding some new dynamics
|
||||
unsigned int max_key_integer = 0;
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
const std::string key = toString(i);
|
||||
const std::string value = toString(i);
|
||||
const bool res = map->insert(key, value); // Will override some from the above
|
||||
if (!res)
|
||||
{
|
||||
ASSERT_LT(2, i);
|
||||
break;
|
||||
}
|
||||
max_key_integer = i;
|
||||
}
|
||||
std::cout << "Max key/value: " << max_key_integer << std::endl;
|
||||
ASSERT_LT(4, max_key_integer);
|
||||
|
||||
// Making sure there is true OOM
|
||||
ASSERT_EQ(0, pool.getNumFreeBlocks());
|
||||
ASSERT_FALSE(map->insert("nonexistent", "value"));
|
||||
ASSERT_FALSE(map->access("nonexistent"));
|
||||
ASSERT_FALSE(map->access("value"));
|
||||
|
||||
// Removing odd values - nearly half of them
|
||||
ASSERT_EQ(2, map->getNumStaticPairs());
|
||||
const unsigned int num_dynamics_old = map->getNumDynamicPairs();
|
||||
map->removeWhere(oddValuePredicate);
|
||||
ASSERT_EQ(2, map->getNumStaticPairs());
|
||||
const unsigned int num_dynamics_new = map->getNumDynamicPairs();
|
||||
std::cout << "Num of dynamic pairs reduced from " << num_dynamics_old << " to " << num_dynamics_new << std::endl;
|
||||
ASSERT_LT(num_dynamics_new, num_dynamics_old);
|
||||
|
||||
// Making sure there's no odd values left
|
||||
for (unsigned int kv_int = 0; kv_int <= max_key_integer; kv_int++)
|
||||
{
|
||||
const std::string* val = map->access(toString(kv_int));
|
||||
if (val)
|
||||
{
|
||||
ASSERT_FALSE(kv_int & 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT_TRUE(kv_int & 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Making sure the memory will be released
|
||||
map.reset();
|
||||
ASSERT_EQ(0, pool.getNumUsedBlocks());
|
||||
}
|
||||
Reference in New Issue
Block a user