diff --git a/include/rcutils/types/hash_map.h b/include/rcutils/types/hash_map.h index 25b69ee6..4d5de731 100644 --- a/include/rcutils/types/hash_map.h +++ b/include/rcutils/types/hash_map.h @@ -144,7 +144,7 @@ rcutils_get_zero_initialized_hash_map(); * ```c * rcutils_hash_map_t hash_map = rcutils_get_zero_initialized_hash_map(); * rcutils_ret_t ret = - * rcutils_hash_map_init(&hash_map, 10, rcutils_get_default_allocator()); + * rcutils_hash_map_init(&hash_map, 2, rcutils_get_default_allocator()); * if (ret != RCUTILS_RET_OK) { * // ... do error handling * } @@ -156,7 +156,8 @@ rcutils_get_zero_initialized_hash_map(); * ``` * * \param[inout] hash_map rcutils_hash_map_t to be initialized - * \param[in] initial_capacity the amount of initial capacity for the hash_map - this must be greater than zero and a power of 2 + * \param[in] initial_capacity the amount of initial capacity for the hash_map - this must be + * greater than zero and will be automatically rounded up to the next power of 2 * \param[in] key_size the size (in bytes) of the key used to index the data * \param[in] data_size the size (in bytes) of the data being stored * \param[in] key_hashing_func a function that returns a hashed value for a key diff --git a/src/hash_map.c b/src/hash_map.c index e9159758..7c06021a 100644 --- a/src/hash_map.c +++ b/src/hash_map.c @@ -230,6 +230,19 @@ static rcutils_ret_t hash_map_check_and_grow_map(rcutils_hash_map_t * hash_map) return ret; } +// Modified from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 +static size_t next_power_of_two(size_t v) +{ + size_t shf = 0; + v--; + for (size_t i = 0; shf < sizeof(size_t) * 4; ++i) { + shf = (((size_t) 1) << i); + v |= v >> shf; + } + v++; + return v > 1 ? v : 1; +} + rcutils_ret_t rcutils_hash_map_init( rcutils_hash_map_t * hash_map, @@ -255,11 +268,10 @@ rcutils_hash_map_init( return RCUTILS_RET_INVALID_ARGUMENT; } - // Due to an optimization we use during lookup, we can currently only handle power-of-two - // capacities. Enforce that here. + // Due to an optimization we use during lookup, the capacity must be a power-of-two. + // If the user passed us something that is not power-of-two, round up to the next power-of-two if ((initial_capacity & (initial_capacity - 1)) != 0) { - RCUTILS_SET_ERROR_MSG("This hashmap only works with power-of-two capacities"); - return RCUTILS_RET_INVALID_ARGUMENT; + initial_capacity = next_power_of_two(initial_capacity); } hash_map->impl = allocator->allocate(sizeof(rcutils_hash_map_impl_t), allocator->state); diff --git a/test/test_hash_map.cpp b/test/test_hash_map.cpp index 2991bfc7..3b9a9f57 100644 --- a/test/test_hash_map.cpp +++ b/test/test_hash_map.cpp @@ -95,11 +95,14 @@ TEST_F(HashMapBaseTest, init_map_initial_capacity_zero_fails) { EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; } -TEST_F(HashMapBaseTest, init_map_initial_capacity_not_power_of_two_fails) { +TEST_F(HashMapBaseTest, init_map_initial_capacity_not_power_of_two) { rcutils_ret_t ret = rcutils_hash_map_init( &map, 3, sizeof(uint32_t), sizeof(uint32_t), test_hash_map_uint32_hash_func, test_uint32_cmp, &allocator); - EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; + ASSERT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + size_t current_capacity = 0; + ASSERT_EQ(RCUTILS_RET_OK, rcutils_hash_map_get_capacity(&map, ¤t_capacity)); + EXPECT_EQ(4u, current_capacity); } TEST_F(HashMapBaseTest, init_map_key_size_zero_fails) {