a/util: Wrap "just enough" of the ring buffer stuff for generic use from C.

This commit is contained in:
Ryan Pavlik 2022-02-08 13:18:15 -06:00
parent 3b0252bda8
commit a904914e6e
6 changed files with 753 additions and 0 deletions

View file

@ -171,6 +171,8 @@ add_library(
util/u_hashmap.h
util/u_hashset.cpp
util/u_hashset.h
util/u_id_ringbuffer.cpp
util/u_id_ringbuffer.h
util/u_json.c
util/u_json.h
util/u_json.hpp

View file

@ -50,6 +50,8 @@ lib_aux_util = static_library(
'util/u_hashmap.h',
'util/u_hashset.cpp',
'util/u_hashset.h',
'util/u_id_ringbuffer.cpp',
'util/u_id_ringbuffer.h',
'util/u_json.c',
'util/u_json.h',
'util/u_json.hpp',

View file

@ -0,0 +1,333 @@
// Copyright 2022, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Wrap some ring buffer internals for somewhat generic C usage.
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
* @ingroup aux_util
*/
#include "u_id_ringbuffer.h"
#include "u_iterator_base.hpp"
#include "u_template_historybuf_impl_helpers.hpp"
#include "u_logging.h"
#include <vector>
#include <memory>
#include <iterator>
#include <type_traits>
using xrt::auxiliary::util::RandomAccessIteratorBase;
using xrt::auxiliary::util::detail::RingBufferHelper;
using id_value_type = uint64_t;
struct u_id_ringbuffer
{
u_id_ringbuffer(uint32_t capacity_) : helper(capacity_), ids(capacity_, 0), capacity(capacity_) {}
RingBufferHelper helper;
std::vector<id_value_type> ids;
uint32_t capacity;
};
namespace {
// just enough iterator to get it done: basically copy and paste from u_template_historybuf_const_iterator.inl
struct IdRingbufferIterator : public RandomAccessIteratorBase<const RingBufferHelper>
{
using base = RandomAccessIteratorBase<const RingBufferHelper>;
using Self = IdRingbufferIterator;
using container_type = const u_id_ringbuffer;
using typename base::difference_type;
using typename base::iterator_category;
using value_type = id_value_type;
using pointer = const value_type *;
using reference = const value_type;
container_type *container_{nullptr};
IdRingbufferIterator(container_type *container, base &&iter_base)
: base(std::move(iter_base)), container_(container)
{}
static Self
begin(container_type &container)
{
return {&container, base::begin(container.helper)};
}
static Self
end(container_type &container)
{
return {&container, base::end(container.helper)};
}
//! returns negative if invalid
int32_t
inner_index() const
{
if (!base::valid()) {
return -1;
}
size_t inner_index = 0;
if (!container_->helper.index_to_inner_index(base::index(), inner_index)) {
return -1;
}
return static_cast<int32_t>(inner_index);
}
//! Dereference operator: throws std::out_of_range if invalid
uint64_t
operator*() const
{
if (!base::valid()) {
throw std::out_of_range("Iterator not valid");
}
size_t inner_index = 0;
if (!container_->helper.index_to_inner_index(base::index(), inner_index)) {
throw std::out_of_range("Iterator not valid");
}
return container_->ids[inner_index];
}
//! Pre-increment: Advance, then return self.
Self &
operator++()
{
this->increment_n(1);
return *this;
}
//! Pre-decrement: Subtract, then return self.
Self &
operator--()
{
this->decrement_n(1);
return *this;
}
};
static_assert(std::is_same<typename std::iterator_traits<IdRingbufferIterator>::iterator_category,
std::random_access_iterator_tag>::value,
"Iterator should be random access");
} // namespace
#define DEFAULT_CATCH(RETURNVAL) \
catch (std::exception const &e) \
{ \
U_LOG_E("Caught exception: %s", e.what()); \
return RETURNVAL; \
} \
catch (...) \
{ \
U_LOG_E("Caught exception"); \
return RETURNVAL; \
}
struct u_id_ringbuffer *
u_id_ringbuffer_create(uint32_t capacity)
{
try {
auto ret = std::make_unique<u_id_ringbuffer>(capacity);
return ret.release();
}
DEFAULT_CATCH(nullptr)
}
// Common wrapping to catch exceptions in functions that return an index or negative for error.
template <typename F>
static inline int64_t
exceptionCatchingWrapper(F &&func)
{
try {
return func();
} catch (std::exception const &e) {
return -1;
}
}
int64_t
u_id_ringbuffer_push_back(struct u_id_ringbuffer *uirb, uint64_t id)
{
try {
auto inner_index = uirb->helper.push_back_location();
uirb->ids[inner_index] = id;
return static_cast<int32_t>(inner_index);
}
DEFAULT_CATCH(-1)
}
void
u_id_ringbuffer_pop_front(struct u_id_ringbuffer *uirb)
{
try {
uirb->helper.pop_front();
}
DEFAULT_CATCH()
}
int32_t
u_id_ringbuffer_get_back(struct u_id_ringbuffer *uirb, uint64_t *out_id)
{
try {
auto inner_index = uirb->helper.back_inner_index();
if (inner_index == uirb->capacity) {
return -1;
}
*out_id = uirb->ids[inner_index];
return static_cast<int32_t>(inner_index);
}
DEFAULT_CATCH(-1)
}
int32_t
u_id_ringbuffer_get_front(struct u_id_ringbuffer *uirb, uint64_t *out_id)
{
try {
auto inner_index = uirb->helper.front_inner_index();
if (inner_index == uirb->capacity) {
return -1;
}
*out_id = uirb->ids[inner_index];
return static_cast<int32_t>(inner_index);
}
DEFAULT_CATCH(-1)
}
uint32_t
u_id_ringbuffer_get_size(struct u_id_ringbuffer const *uirb)
{
try {
return static_cast<int32_t>(uirb->helper.size());
}
DEFAULT_CATCH(0)
}
bool
u_id_ringbuffer_is_empty(struct u_id_ringbuffer const *uirb)
{
try {
return uirb->helper.empty();
}
DEFAULT_CATCH(true)
}
// Handle conditional setting of value pointed to by out_id, and casting of inner_index
static inline int32_t
handleGetterResult(struct u_id_ringbuffer const *uirb, size_t inner_index, uint64_t *out_id)
{
if (out_id != nullptr) {
*out_id = uirb->ids[inner_index];
}
return static_cast<int32_t>(inner_index);
}
int32_t
u_id_ringbuffer_get_at_age(struct u_id_ringbuffer *uirb, uint32_t age, uint64_t *out_id)
{
try {
size_t inner_index = 0;
if (!uirb->helper.age_to_inner_index(age, inner_index)) {
// out of range
return -1;
}
return handleGetterResult(uirb, inner_index, out_id);
}
DEFAULT_CATCH(-1)
}
int32_t
u_id_ringbuffer_get_at_clamped_age(struct u_id_ringbuffer *uirb, uint32_t age, uint64_t *out_id)
{
try {
size_t inner_index = 0;
if (!uirb->helper.clamped_age_to_inner_index(age, inner_index)) {
// out of range
return -1;
}
return handleGetterResult(uirb, inner_index, out_id);
}
DEFAULT_CATCH(-1)
}
int32_t
u_id_ringbuffer_get_at_index(struct u_id_ringbuffer *uirb, uint32_t index, uint64_t *out_id)
{
try {
size_t inner_index = 0;
if (!uirb->helper.index_to_inner_index(index, inner_index)) {
// out of range
return -1;
}
return handleGetterResult(uirb, inner_index, out_id);
}
DEFAULT_CATCH(-1)
}
// Handle validity of iterators, conditional setting of values pointed to by out_id and out_index, and return value
static inline int32_t
handleAlgorithmResult(IdRingbufferIterator it, uint64_t *out_id, uint32_t *out_index)
{
if (!it.valid()) {
return -1;
}
if (out_id != nullptr) {
*out_id = *it;
}
if (out_index != nullptr) {
*out_index = it.index();
}
return it.inner_index();
}
int32_t
u_id_ringbuffer_lower_bound_id(struct u_id_ringbuffer *uirb, uint64_t search_id, uint64_t *out_id, uint32_t *out_index)
{
try {
const auto b = IdRingbufferIterator::begin(*uirb);
const auto e = IdRingbufferIterator::end(*uirb);
// find the first element *not less than* our ID: binary search
const auto it = std::lower_bound(b, e, search_id);
return handleAlgorithmResult(it, out_id, out_index);
}
DEFAULT_CATCH(-1)
}
int32_t
u_id_ringbuffer_find_id_unordered(struct u_id_ringbuffer *uirb,
uint64_t search_id,
uint64_t *out_id,
uint32_t *out_index)
{
try {
const auto b = IdRingbufferIterator::begin(*uirb);
const auto e = IdRingbufferIterator::end(*uirb);
// find the matching ID with simple linear search
const auto it = std::find(b, e, search_id);
return handleAlgorithmResult(it, out_id, out_index);
}
DEFAULT_CATCH(-1)
}
void
u_id_ringbuffer_destroy(struct u_id_ringbuffer **ptr_to_uirb)
{
try {
if (ptr_to_uirb == nullptr) {
return;
}
struct u_id_ringbuffer *uirb = *ptr_to_uirb;
if (uirb == nullptr) {
return;
}
delete uirb;
*ptr_to_uirb = nullptr;
}
DEFAULT_CATCH()
}

View file

@ -0,0 +1,211 @@
// Copyright 2022, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Ring buffer for things keyed on an ID but otherwise maintained externally, for C usage.
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
* @ingroup aux_util
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Container type to let you store IDs in a ring buffer, and maybe your own data in your own parallel array.
*
* The IDs are just uint64_t. If you don't need any of the order-dependent functionality, you can treat use them for any
* purpose you like.
*
* Some functionality requires that IDs be pushed in increasing order, but it's highlighted in the docs.
* If you need more than this, either extend this or use the underlying C++ container if that's an OK solution for you.
*
*/
struct u_id_ringbuffer;
/**
* Create a ringbuffer for storing IDs.
*
* You might keep an array of equivalent capacity locally: methods of this container will tell you which index in that
* array to interact with.
*
* @param capacity
* @return struct u_id_ringbuffer*
*
* @public @memberof u_id_ringbuffer
*/
struct u_id_ringbuffer *
u_id_ringbuffer_create(uint32_t capacity);
/**
* Push a new element to the back
*
* @param uirb self pointer
* @param id The ID to push back. It is your responsibility to make sure you insert in order if you want to use ordered
* methods.
* @return the "inner" index in your array to store any associated data, or negative if error.
*
* @public @memberof u_id_ringbuffer
*/
int64_t
u_id_ringbuffer_push_back(struct u_id_ringbuffer *uirb, uint64_t id);
/**
* Pop an element from the front, if any
*
* @param uirb self pointer.
*
* @public @memberof u_id_ringbuffer
*/
void
u_id_ringbuffer_pop_front(struct u_id_ringbuffer *uirb);
/**
* Get the back (most recent) of the buffer
*
* @param uirb self pointer
* @param[out] out_id Where to store the back ID
* @return the "inner" index in your array with any associated data, or negative if error (empty, etc).
*
* @public @memberof u_id_ringbuffer
*/
int32_t
u_id_ringbuffer_get_back(struct u_id_ringbuffer *uirb, uint64_t *out_id);
/**
* Get the front (least recent) of the buffer
*
* @param uirb self pointer
* @param[out] out_id Where to store the front ID
* @return the "inner" index in your array with any associated data, or negative if error (empty, etc).
*
* @public @memberof u_id_ringbuffer
*/
int32_t
u_id_ringbuffer_get_front(struct u_id_ringbuffer *uirb, uint64_t *out_id);
/**
* Get the number of elements in the buffer
*
* @param uirb self pointer
* @return the size
*
* @public @memberof u_id_ringbuffer
*/
uint32_t
u_id_ringbuffer_get_size(struct u_id_ringbuffer const *uirb);
/**
* Get whether the buffer is empty
*
* @param uirb self pointer
* @return true if empty
*
* @public @memberof u_id_ringbuffer
*/
bool
u_id_ringbuffer_is_empty(struct u_id_ringbuffer const *uirb);
/**
* Get an element a certain distance ("age") from the back of the buffer
*
* See u_id_ringbuffer_get_at_clamped_age() if you want to clamp the age
*
* @param uirb self pointer
* @param age the distance from the back (0 is the same as back, 1 is previous, etc)
* @param[out] out_id Where to store the ID, if successful (optional)
* @return the "inner" index in your array with any associated data, or negative if error (empty, out of range, etc).
*
* @public @memberof u_id_ringbuffer
*/
int32_t
u_id_ringbuffer_get_at_age(struct u_id_ringbuffer *uirb, uint32_t age, uint64_t *out_id);
/**
* Get an element a certain distance ("age") from the back of the buffer, clamping age to stay in bounds as long as the
* buffer is not empty.
*
* See u_id_ringbuffer_get_at_age() if you don't want clamping.
*
* @param uirb self pointer
* @param age the distance from the back (0 is the same as back, 1 is previous, etc)
* @param[out] out_id Where to store the ID, if successful (optional)
* @return the "inner" index in your array with any associated data, or negative if error (empty, etc).
*
* @public @memberof u_id_ringbuffer
*/
int32_t
u_id_ringbuffer_get_at_clamped_age(struct u_id_ringbuffer *uirb, uint32_t age, uint64_t *out_id);
/**
* Get an element a certain index from the front of the (logical) buffer
*
* @param uirb self pointer
* @param index the distance from the front (0 is the same as front, 1 is newer, etc)
* @param[out] out_id Where to store the ID, if successful (optional)
* @return the "inner" index in your array with any associated data, or negative if error (empty, out of range, etc).
*
* @public @memberof u_id_ringbuffer
*/
int32_t
u_id_ringbuffer_get_at_index(struct u_id_ringbuffer *uirb, uint32_t index, uint64_t *out_id);
/**
* Find the latest element not less than the supplied ID @p search_id .
*
* Assumes/depends on your maintenance of entries in ascending order. If you aren't ensuring this, use
* u_id_ringbuffer_find_id_unordered() instead.
*
* (Wraps `std::lower_bound`)
*
* @param uirb self pointer
* @param search_id the ID to search for.
* @param[out] out_id Where to store the ID found, if successful (optional)
* @param[out] out_index Where to store the ring buffer index (not the inner index) found, if successful (optional)
* @return the "inner" index in your array with any associated data, or negative if error (empty, etc).
*
* @public @memberof u_id_ringbuffer
*/
int32_t
u_id_ringbuffer_lower_bound_id(struct u_id_ringbuffer *uirb, uint64_t search_id, uint64_t *out_id, uint32_t *out_index);
/**
* Find the element with the supplied ID @p search_id in an unordered buffer.
*
* This does *not* depend on order so just does a linear search. If you are keeping your IDs in ascending order, use
* u_id_ringbuffer_lower_bound_id() instead.
*
* @param uirb self pointer
* @param search_id the ID to search for.
* @param[out] out_id Where to store the ID found, if successful (optional)
* @param[out] out_index Where to store the ring buffer index (not the inner index) found, if successful (optional)
* @return the "inner" index in your array with any associated data, or negative if error (empty, etc).
*
* @public @memberof u_id_ringbuffer
*/
int32_t
u_id_ringbuffer_find_id_unordered(struct u_id_ringbuffer *uirb,
uint64_t search_id,
uint64_t *out_id,
uint32_t *out_index);
/**
* Destroy an ID ring buffer.
*
* Does null checks.
*
* @param ptr_to_uirb Address of your ring buffer pointer. Will be set to zero.
*
* @public @memberof u_id_ringbuffer
*/
void
u_id_ringbuffer_destroy(struct u_id_ringbuffer **ptr_to_uirb);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -35,6 +35,12 @@ target_link_libraries(tests_history_buf PRIVATE tests_main)
target_link_libraries(tests_history_buf PRIVATE aux_util aux_math)
add_test(NAME tests_history_buf COMMAND tests_history_buf --success)
# id ring buffer (closely related to history buf)
add_executable(tests_id_ringbuffer tests_id_ringbuffer.cpp)
target_link_libraries(tests_id_ringbuffer PRIVATE tests_main)
target_link_libraries(tests_id_ringbuffer PRIVATE aux_util)
add_test(NAME tests_id_ringbuffer COMMAND tests_id_ringbuffer --success)
# pacing
add_executable(tests_pacing tests_pacing.cpp)
target_link_libraries(tests_pacing PRIVATE tests_main)

View file

@ -0,0 +1,199 @@
// Copyright 2021-2022, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief u_id_ringbuffer collection tests.
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
*/
#include <util/u_id_ringbuffer.h>
#include "catch/catch.hpp"
TEST_CASE("u_template_historybuf")
{
auto buffer = u_id_ringbuffer_create(4);
SECTION("behavior when empty")
{
CHECK(u_id_ringbuffer_is_empty(buffer));
CHECK(0 == u_id_ringbuffer_get_size(buffer));
uint64_t out_id = 0;
CHECK(u_id_ringbuffer_get_front(buffer, &out_id) < 0);
CHECK(u_id_ringbuffer_get_back(buffer, &out_id) < 0);
}
SECTION("behavior with one")
{
int32_t zero_inner_index;
REQUIRE((zero_inner_index = u_id_ringbuffer_push_back(buffer, 0)) >= 0);
CHECK_FALSE(u_id_ringbuffer_is_empty(buffer));
CHECK(1 == u_id_ringbuffer_get_size(buffer));
CAPTURE(zero_inner_index);
// check front/back
uint64_t out_id_front = 55;
uint64_t out_id_back = 55;
CHECK(u_id_ringbuffer_get_front(buffer, &out_id_front) >= 0);
CHECK(u_id_ringbuffer_get_front(buffer, &out_id_front) == zero_inner_index);
CHECK(out_id_front == 0);
CHECK(u_id_ringbuffer_get_back(buffer, &out_id_back) >= 0);
CHECK(u_id_ringbuffer_get_back(buffer, &out_id_front) == zero_inner_index);
CHECK(out_id_back == 0);
CHECK(out_id_front == out_id_back);
// check contents
{
uint64_t out_id = 55;
CHECK(u_id_ringbuffer_get_at_index(buffer, 0, &out_id) == zero_inner_index);
CHECK(out_id == 0);
}
{
uint64_t out_id = 55;
CHECK(u_id_ringbuffer_get_at_age(buffer, 0, &out_id) == zero_inner_index);
CHECK(out_id == 0);
}
{
uint64_t out_id = 55;
CHECK(u_id_ringbuffer_get_at_clamped_age(buffer, 0, &out_id) == zero_inner_index);
CHECK(out_id == 0);
}
{
uint64_t out_id = 55;
CHECK(u_id_ringbuffer_get_at_age(buffer, 1, &out_id) < 0);
}
{
uint64_t out_id = 55;
CHECK(u_id_ringbuffer_get_at_clamped_age(buffer, 1, &out_id) == zero_inner_index);
CHECK(out_id == 0);
}
{
uint64_t out_id = 55;
CHECK(u_id_ringbuffer_get_at_clamped_age(buffer, 2, &out_id) == zero_inner_index);
CHECK(out_id == 0);
}
}
SECTION("behavior with two")
{
int32_t zero_inner_index;
int32_t one_inner_index;
REQUIRE((zero_inner_index = u_id_ringbuffer_push_back(buffer, 0)) >= 0);
REQUIRE((one_inner_index = u_id_ringbuffer_push_back(buffer, 1)) >= 0);
REQUIRE(zero_inner_index != one_inner_index);
CAPTURE(zero_inner_index);
CAPTURE(one_inner_index);
CHECK_FALSE(u_id_ringbuffer_is_empty(buffer));
CHECK(2 == u_id_ringbuffer_get_size(buffer));
SECTION("check front and back")
{
// check front/back
uint64_t out_id_front = 55;
uint64_t out_id_back = 55;
CHECK(u_id_ringbuffer_get_front(buffer, &out_id_front) == zero_inner_index);
CHECK(out_id_front == 0);
CHECK(u_id_ringbuffer_get_back(buffer, &out_id_back) == one_inner_index);
CHECK(out_id_back == 1);
CHECK(out_id_front != out_id_back);
}
SECTION("check contents")
{
// check contents
{
uint64_t out_id = 55;
CHECK(u_id_ringbuffer_get_at_index(buffer, 0, &out_id) == zero_inner_index);
CHECK(out_id == 0);
}
{
uint64_t out_id = 55;
CHECK(u_id_ringbuffer_get_at_index(buffer, 1, &out_id) == one_inner_index);
CHECK(out_id == 1);
}
{
uint64_t out_id = 55;
CHECK(u_id_ringbuffer_get_at_index(buffer, 2, &out_id) < 0);
}
{
uint64_t out_id = 55;
CHECK(u_id_ringbuffer_get_at_age(buffer, 0, &out_id) == one_inner_index);
CHECK(out_id == 1);
}
{
uint64_t out_id = 55;
CHECK(u_id_ringbuffer_get_at_clamped_age(buffer, 0, &out_id) == one_inner_index);
CHECK(out_id == 1);
}
{
uint64_t out_id = 55;
CHECK(u_id_ringbuffer_get_at_age(buffer, 1, &out_id) == zero_inner_index);
CHECK(out_id == 0);
}
{
uint64_t out_id = 55;
CHECK(u_id_ringbuffer_get_at_clamped_age(buffer, 1, &out_id) == zero_inner_index);
CHECK(out_id == 0);
}
{
uint64_t out_id = 55;
CHECK(u_id_ringbuffer_get_at_age(buffer, 2, &out_id) < 0);
}
{
uint64_t out_id = 55;
CHECK(u_id_ringbuffer_get_at_clamped_age(buffer, 2, &out_id) == zero_inner_index);
CHECK(out_id == 0);
}
{
uint64_t out_id = 55;
CHECK(u_id_ringbuffer_get_at_clamped_age(buffer, 3, &out_id) == zero_inner_index);
CHECK(out_id == 0);
}
}
}
SECTION("algorithm behavior with 3")
{
int32_t zero_inner_index;
int32_t two_inner_index;
int32_t four_inner_index;
REQUIRE((zero_inner_index = u_id_ringbuffer_push_back(buffer, 0)) >= 0);
REQUIRE((two_inner_index = u_id_ringbuffer_push_back(buffer, 2)) >= 0);
REQUIRE((four_inner_index = u_id_ringbuffer_push_back(buffer, 4)) >= 0);
CAPTURE(zero_inner_index);
CAPTURE(two_inner_index);
CAPTURE(four_inner_index);
CHECK_FALSE(u_id_ringbuffer_is_empty(buffer));
CHECK(3 == u_id_ringbuffer_get_size(buffer));
uint64_t out_id = 55;
uint32_t out_index = 55;
CHECK(u_id_ringbuffer_find_id_unordered(buffer, 0, nullptr, nullptr) == zero_inner_index);
CHECK(u_id_ringbuffer_find_id_unordered(buffer, 0, &out_id, &out_index) == zero_inner_index);
CHECK(out_id == 0);
CHECK(out_index == 0);
CHECK(u_id_ringbuffer_find_id_unordered(buffer, 2, nullptr, nullptr) == two_inner_index);
CHECK(u_id_ringbuffer_find_id_unordered(buffer, 2, &out_id, &out_index) == two_inner_index);
CHECK(out_id == 2);
CHECK(out_index == 1);
CHECK(u_id_ringbuffer_find_id_unordered(buffer, 4, nullptr, nullptr) == four_inner_index);
CHECK(u_id_ringbuffer_find_id_unordered(buffer, 4, &out_id, &out_index) == four_inner_index);
CHECK(out_id == 4);
CHECK(out_index == 2);
// first id not less than 1 is id 2.
out_id = 55;
out_index = 55;
CHECK(u_id_ringbuffer_lower_bound_id(buffer, 1, nullptr, nullptr) == two_inner_index);
CHECK(u_id_ringbuffer_lower_bound_id(buffer, 1, &out_id, &out_index) == two_inner_index);
CHECK(out_id == 2);
CHECK(out_index == 1);
}
u_id_ringbuffer_destroy(&buffer);
}