xbot/linebuffer.hpp

98 lines
2.4 KiB
C++
Raw Permalink Normal View History

2023-11-22 19:59:34 -08:00
#pragma once
/**
* @file linebuffer.hpp
* @author Eric Mertens <emertens@gmail.com>
* @brief A line buffering class
* @version 0.1
* @date 2023-08-22
*
* @copyright Copyright (c) 2023
*
*/
#include <boost/asio/buffer.hpp>
#include <algorithm>
#include <concepts>
#include <vector>
/**
* @brief Fixed-size buffer with line-oriented dispatch
*
*/
class LineBuffer
{
std::vector<char> buffer;
// [buffer.begin(), end_) contains buffered data
// [end_, buffer.end()) is available buffer space
std::vector<char>::iterator end_;
public:
/**
* @brief Construct a new Line Buffer object
*
* @param n Buffer size
*/
LineBuffer(std::size_t n) : buffer(n), end_{buffer.begin()} {}
/**
* @brief Get the available buffer space
*
* @return boost::asio::mutable_buffer
*/
auto get_buffer() -> boost::asio::mutable_buffer
{
return boost::asio::buffer(&*end_, std::distance(end_, buffer.end()));
}
/**
* @brief Commit new buffer bytes and dispatch line callback
*
* The first n bytes of the buffer will be considered to be
* populated. The line callback function will be called once
* per completed line. Those lines are removed from the buffer
* and the is ready for additional calls to get_buffer and
* add_bytes.
*
* @param n Bytes written to the last call of get_buffer
* @param line_cb Callback function to run on each completed line
*/
auto add_bytes(std::size_t n, std::invocable<char *> auto line_cb) -> void
{
auto const start = end_;
std::advance(end_, n);
// new data is now located in [start, end_)
// cursor marks the beginning of the current line
auto cursor = buffer.begin();
for (auto nl = std::find(start, end_, '\n');
nl != end_;
nl = std::find(cursor, end_, '\n'))
{
// Null-terminate the line. Support both \n and \r\n
if (cursor < nl && *std::prev(nl) == '\r')
{
*std::prev(nl) = '\0';
}
else
{
*nl = '\0';
}
line_cb(&*cursor);
cursor = std::next(nl);
}
// If any lines were processed, move all processed lines to
// the front of the buffer
if (cursor != buffer.begin())
{
end_ = std::move(cursor, end_, buffer.begin());
}
}
};