Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ auto receiver(std::shared_ptr<connection> conn) -> asio::awaitable<void>

// The response must be consumed without suspending the
// coroutine i.e. without the use of async operations.
for (auto const& elem : resp.value().get_view())
for (auto const& elem : resp.value())
std::cout << elem.value << "\n";

std::cout << std::endl;
Expand Down
2 changes: 1 addition & 1 deletion doc/modules/ROOT/pages/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ auto receiver(std::shared_ptr<connection> conn) -> asio::awaitable<void>

// The response must be consumed without suspending the
// coroutine i.e. without the use of async operations.
for (auto const& elem : resp.value().get_view())
for (auto const& elem : resp.value())
std::cout << elem.value << "\n";

std::cout << std::endl;
Expand Down
2 changes: 1 addition & 1 deletion example/cpp20_chat_room.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ auto receiver(std::shared_ptr<connection> conn) -> awaitable<void>

// The response must be consumed without suspending the
// coroutine i.e. without the use of async operations.
for (auto const& elem : resp.value().get_view())
for (auto const& elem : resp.value())
std::cout << elem.value << "\n";

std::cout << std::endl;
Expand Down
2 changes: 1 addition & 1 deletion example/cpp20_subscriber.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ auto receiver(std::shared_ptr<connection> conn) -> asio::awaitable<void>

// The response must be consumed without suspending the
// coroutine i.e. without the use of async operations.
for (auto const& elem : resp.value().get_view())
for (auto const& elem : resp.value())
std::cout << elem.value << "\n";

std::cout << std::endl;
Expand Down
16 changes: 11 additions & 5 deletions include/boost/redis/impl/flat_tree.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
#include <boost/redis/resp3/tree.hpp>

#include <boost/assert.hpp>
#include <boost/throw_exception.hpp>

#include <algorithm>
#include <cstddef>
#include <cstring>
#include <stdexcept>
#include <string_view>

namespace boost::redis::resp3 {
Expand Down Expand Up @@ -228,15 +230,19 @@ void flat_tree::notify_done()
data_tmp_offset_ = data_.size;
}

const node_view& flat_tree::at(std::size_t i) const
{
if (i >= size())
BOOST_THROW_EXCEPTION(std::out_of_range("flat_tree::at"));
return view_tree_[i];
}

bool operator==(flat_tree const& a, flat_tree const& b)
{
// data is already taken into account by comparing the nodes.
// Only committed nodes should be taken into account.
auto a_nodes = a.get_view();
auto b_nodes = b.get_view();
return a_nodes.size() == b_nodes.size() &&
std::equal(a_nodes.begin(), a_nodes.end(), b_nodes.begin()) &&
a.total_msgs_ == b.total_msgs_;
return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin()) &&
a.get_total_msgs() == b.get_total_msgs();
}

} // namespace boost::redis::resp3
172 changes: 152 additions & 20 deletions include/boost/redis/resp3/flat_tree.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <boost/core/span.hpp>

#include <cstddef>
#include <iterator>
#include <memory>

namespace boost::redis {
Expand Down Expand Up @@ -45,15 +46,35 @@ struct flat_buffer {
* to obtain how many responses this object contains.
*
* Objects are typically created by the user and passed to @ref connection::async_exec
* to be used as response containers. Call @ref get_view to access the actual RESP3 nodes.
* Once populated, `flat_tree` can't be modified, except for @ref clear and assignment.
* to be used as response containers. Once populated, they can be used as a const range
* of @ref resp3::node_view objects. The usual random access range methods (like @ref at, @ref size or
* @ref front) are provided. Once populated, `flat_tree` can't be modified,
* except for @ref clear and assignment.
*
* `flat_tree` models `std::ranges::contiguous_range`.
*
* A `flat_tree` is conceptually similar to a pair of `std::vector` objects, one holding
* @ref resp3::node_view objects, and another owning the the string data that these views
* point to. The node capacity and the data capacity are the capacities of these two vectors.
*/
class flat_tree {
public:
/**
* @brief The type of the iterators returned by @ref begin and @ref end.
*
* It is guaranteed to be a contiguous iterator. While this is currently a pointer,
* users shouldn't rely on this fact, as the exact implementation may change between releases.
*/
using iterator = const node_view*;

/**
* @brief The type of the iterators returned by @ref rbegin and @ref rend.
*
* As with @ref iterator, users should treat this type as an unspecified
* contiguous iterator type rather than assuming a specific type.
*/
using reverse_iterator = std::reverse_iterator<iterator>;

/**
* @brief Default constructor.
*
Expand All @@ -70,7 +91,7 @@ class flat_tree {
* Constructs a tree by taking ownership of the nodes in `other`.
*
* @par Object lifetimes
* References to the nodes and strings in `other` remain valid.
* Iterators, pointers and references to the nodes and strings in `other` remain valid.
*
* @par Exception safety
* No-throw guarantee.
Expand All @@ -95,8 +116,8 @@ class flat_tree {
* `other` is left in a valid but unspecified state.
*
* @par Object lifetimes
* References to the nodes and strings in `other` remain valid.
* References to the nodes and strings in `*this` are invalidated.
* Iterators, pointers and references to the nodes and strings in `other` remain valid.
* Iterators, pointers and references to the nodes and strings in `*this` are invalidated.
*
* @par Exception safety
* No-throw guarantee.
Expand All @@ -110,16 +131,137 @@ class flat_tree {
* After the copy, `*this` and `other` have independent lifetimes (usual copy semantics).
*
* @par Object lifetimes
* References to the nodes and strings in `*this` are invalidated.
* Iterators, pointers and references to the nodes and strings in `*this` are invalidated.
*
* @par Exception safety
* Basic guarantee. Memory allocations might throw.
*/
flat_tree& operator=(const flat_tree& other);

friend bool operator==(flat_tree const&, flat_tree const&);
/**
* @brief Returns an iterator to the first element of the node range.
*
* @par Exception safety
* No-throw guarantee.
*
* @returns An iterator to the first node.
*/
iterator begin() const noexcept { return data(); }

friend bool operator!=(flat_tree const&, flat_tree const&);
/**
* @brief Returns an iterator past the last element in the node range.
*
* @par Exception safety
* No-throw guarantee.
*
* @returns An iterator past the last element in the node range.
*/
iterator end() const noexcept { return data() + size(); }

/**
* @brief Returns an iterator to the first element of the reversed node range.
*
* Allows iterating the range of nodes in reverse order.
*
* @par Exception safety
* No-throw guarantee.
*
* @returns An iterator to the first node of the reversed range.
*/
reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; }

/**
* @brief Returns an iterator past the last element of the reversed node range.
*
* Allows iterating the range of nodes in reverse order.
*
* @par Exception safety
* No-throw guarantee.
*
* @returns An iterator past the last element of the reversed node range.
*/
reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; }

/**
* @brief Returns a reference to the node at the specified position (checked access).
*
* @par Exception safety
* Strong guarantee. Throws `std::out_of_range` if `i >= size()`.
*
* @param i Position of the node to return.
* @returns A reference to the node at position `i`.
*/
const node_view& at(std::size_t i) const;

/**
* @brief Returns a reference to the node at the specified position (unchecked access).
*
* @par Precondition
* `i < size()`.
*
* @par Exception safety
* No-throw guarantee.
*
* @param i Position of the node to return.
* @returns A reference to the node at position `i`.
*/
const node_view& operator[](std::size_t i) const noexcept { return get_view()[i]; }

/**
* @brief Returns a reference to the first node.
*
* @par Precondition
* `!empty()`.
*
* @par Exception safety
* No-throw guarantee.
*
* @returns A reference to the first node.
*/
const node_view& front() const noexcept { return get_view().front(); }

/**
* @brief Returns a reference to the last node.
*
* @par Precondition
* `!empty()`.
*
* @par Exception safety
* No-throw guarantee.
*
* @returns A reference to the last node.
*/
const node_view& back() const noexcept { return get_view().back(); }

/**
* @brief Returns a pointer to the underlying node storage.
*
* @par Exception safety
* No-throw guarantee.
*
* @returns A pointer to the underlying node array.
*/
const node_view* data() const noexcept { return view_tree_.data(); }

/**
* @brief Checks whether the tree is empty.
*
* @par Exception safety
* No-throw guarantee.
*
* @returns `true` if the tree contains no nodes, `false` otherwise.
*/
bool empty() const noexcept { return size() == 0u; }

/**
* @brief Returns the number of nodes in the tree.
*
* @par Exception safety
* No-throw guarantee.
*
* @returns The number of nodes.
*/
std::size_t size() const noexcept { return node_tmp_offset_; }

/** @brief Reserves capacity for incoming data.
*
Expand All @@ -142,7 +284,7 @@ class flat_tree {
/** @brief Clears the tree so it contains no nodes.
*
* Calling this function removes every node, making
* @ref get_view return empty and @ref get_total_msgs
* the range contain no nodes, and @ref get_total_msgs
* return zero. It does not modify the object's capacity.
*
* To re-use a `flat_tree` for several requests,
Expand Down Expand Up @@ -189,17 +331,6 @@ class flat_tree {
*/
auto data_capacity() const noexcept -> std::size_t { return data_.capacity; }

/** @brief Returns a vector with the nodes in the tree.
*
* This is the main way to access the contents of the tree.
*
* @par Exception safety
* No-throw guarantee.
*
* @returns The nodes in the tree.
*/
span<const node_view> get_view() const noexcept { return {view_tree_.data(), node_tmp_offset_}; }

/** @brief Returns the number of memory reallocations that took place in the data buffer.
*
* This function returns how many reallocations in the data buffer were performed and
Expand All @@ -226,6 +357,7 @@ class flat_tree {
private:
template <class> friend class adapter::detail::general_aggregate;

span<const node_view> get_view() const noexcept { return {data(), size()}; }
void notify_init();
void notify_done();

Expand Down
2 changes: 1 addition & 1 deletion test/test_conn_exec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ BOOST_AUTO_TEST_CASE(exec_generic_flat_response)
BOOST_TEST_REQUIRE(finished);

BOOST_TEST(resp.has_value());
BOOST_TEST(resp->get_view().front().value == "PONG");
BOOST_TEST(resp.value().front().value == "PONG");
}

} // namespace
8 changes: 4 additions & 4 deletions test/test_conn_push2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -682,15 +682,15 @@ struct test_pubsub_state_restoration_impl {
{
// Checks for the expected subscriptions and patterns after restoration
std::set<std::string_view> seen_channels, seen_patterns;
for (auto it = resp_push.get_view().begin(); it != resp_push.get_view().end();) {
for (auto it = resp_push.begin(); it != resp_push.end();) {
// The root element should be a push
BOOST_TEST_EQ(it->data_type, type::push);
BOOST_TEST_GE(it->aggregate_size, 2u);
BOOST_TEST(++it != resp_push.get_view().end());
BOOST_TEST(++it != resp_push.end());

// The next element should be the message type
std::string_view msg_type = it->value;
BOOST_TEST(++it != resp_push.get_view().end());
BOOST_TEST(++it != resp_push.end());

// The next element is the channel or pattern
if (msg_type == "subscribe")
Expand All @@ -699,7 +699,7 @@ struct test_pubsub_state_restoration_impl {
seen_patterns.insert(it->value);

// Skip the rest of the nodes
while (it != resp_push.get_view().end() && it->depth != 0u)
while (it != resp_push.end() && it->depth != 0u)
++it;
}

Expand Down
Loading