mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 00:50:22 +00:00
AK: Add CircularBuffer
The class is very similar to `CircularDuplexStream` in its behavior. Main differences are that `CircularBuffer`: - does not inherit from `AK::Stream` - uses `ErrorOr` for its API - is heap allocated (and OOM-Safe) This patch also add some tests.
This commit is contained in:
parent
3454891d38
commit
f12e81b74a
Notes:
sideshowbarker
2024-07-17 04:21:32 +09:00
Author: https://github.com/LucasChollet Commit: https://github.com/SerenityOS/serenity/commit/f12e81b74a Pull-request: https://github.com/SerenityOS/serenity/pull/16437 Reviewed-by: https://github.com/ADKaster ✅ Reviewed-by: https://github.com/timschumi ✅
6 changed files with 488 additions and 0 deletions
|
@ -1,6 +1,7 @@
|
|||
set(AK_SOURCES
|
||||
Assertions.cpp
|
||||
Base64.cpp
|
||||
CircularBuffer.cpp
|
||||
DeprecatedString.cpp
|
||||
FloatingPointStringConversions.cpp
|
||||
FlyString.cpp
|
||||
|
|
140
AK/CircularBuffer.cpp
Normal file
140
AK/CircularBuffer.cpp
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Lucas Chollet <lucas.chollet@free.fr>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/CircularBuffer.h>
|
||||
#include <AK/MemMem.h>
|
||||
|
||||
namespace AK {
|
||||
|
||||
CircularBuffer::CircularBuffer(ByteBuffer buffer)
|
||||
: m_buffer(move(buffer))
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<CircularBuffer> CircularBuffer::create_empty(size_t size)
|
||||
{
|
||||
auto temporary_buffer = TRY(ByteBuffer::create_uninitialized(size));
|
||||
|
||||
CircularBuffer circular_buffer { move(temporary_buffer) };
|
||||
|
||||
return circular_buffer;
|
||||
}
|
||||
|
||||
ErrorOr<CircularBuffer> CircularBuffer::create_initialized(ByteBuffer buffer)
|
||||
{
|
||||
CircularBuffer circular_buffer { move(buffer) };
|
||||
|
||||
circular_buffer.m_used_space = circular_buffer.m_buffer.size();
|
||||
|
||||
return circular_buffer;
|
||||
}
|
||||
|
||||
size_t CircularBuffer::empty_space() const
|
||||
{
|
||||
return capacity() - m_used_space;
|
||||
}
|
||||
|
||||
size_t CircularBuffer::used_space() const
|
||||
{
|
||||
return m_used_space;
|
||||
}
|
||||
|
||||
size_t CircularBuffer::capacity() const
|
||||
{
|
||||
return m_buffer.size();
|
||||
}
|
||||
|
||||
bool CircularBuffer::is_wrapping_around() const
|
||||
{
|
||||
return capacity() <= m_reading_head + m_used_space;
|
||||
}
|
||||
|
||||
Optional<size_t> CircularBuffer::offset_of(StringView needle, Optional<size_t> until) const
|
||||
{
|
||||
auto const read_until = until.value_or(m_used_space);
|
||||
|
||||
Array<ReadonlyBytes, 2> spans {};
|
||||
spans[0] = next_read_span();
|
||||
|
||||
if (spans[0].size() > read_until)
|
||||
spans[0] = spans[0].trim(read_until);
|
||||
else if (is_wrapping_around())
|
||||
spans[1] = m_buffer.span().slice(0, read_until - spans[0].size());
|
||||
|
||||
return AK::memmem(spans.begin(), spans.end(), needle.bytes());
|
||||
}
|
||||
|
||||
void CircularBuffer::clear()
|
||||
{
|
||||
m_reading_head = 0;
|
||||
m_used_space = 0;
|
||||
}
|
||||
|
||||
Bytes CircularBuffer::next_write_span()
|
||||
{
|
||||
if (is_wrapping_around())
|
||||
return m_buffer.span().slice(m_reading_head + m_used_space - capacity(), capacity() - m_used_space);
|
||||
return m_buffer.span().slice(m_reading_head + m_used_space, capacity() - (m_reading_head + m_used_space));
|
||||
}
|
||||
|
||||
ReadonlyBytes CircularBuffer::next_read_span() const
|
||||
{
|
||||
return m_buffer.span().slice(m_reading_head, min(capacity() - m_reading_head, m_used_space));
|
||||
}
|
||||
|
||||
size_t CircularBuffer::write(ReadonlyBytes bytes)
|
||||
{
|
||||
auto remaining = bytes.size();
|
||||
|
||||
while (remaining > 0) {
|
||||
auto const next_span = next_write_span();
|
||||
if (next_span.size() == 0)
|
||||
break;
|
||||
|
||||
auto const written_bytes = bytes.slice(bytes.size() - remaining).copy_trimmed_to(next_span);
|
||||
|
||||
m_used_space += written_bytes;
|
||||
|
||||
remaining -= written_bytes;
|
||||
}
|
||||
|
||||
return bytes.size() - remaining;
|
||||
}
|
||||
|
||||
Bytes CircularBuffer::read(Bytes bytes)
|
||||
{
|
||||
auto remaining = bytes.size();
|
||||
|
||||
while (remaining > 0) {
|
||||
auto const next_span = next_read_span();
|
||||
if (next_span.size() == 0)
|
||||
break;
|
||||
|
||||
auto written_bytes = next_span.copy_trimmed_to(bytes.slice(bytes.size() - remaining));
|
||||
|
||||
m_used_space -= written_bytes;
|
||||
m_reading_head += written_bytes;
|
||||
|
||||
if (m_reading_head >= capacity())
|
||||
m_reading_head -= capacity();
|
||||
|
||||
remaining -= written_bytes;
|
||||
}
|
||||
|
||||
return bytes.trim(bytes.size() - remaining);
|
||||
}
|
||||
|
||||
ErrorOr<void> CircularBuffer::discard(size_t discarding_size)
|
||||
{
|
||||
if (m_used_space < discarding_size)
|
||||
return Error::from_string_literal("Can not discard more data than what the buffer contains");
|
||||
m_used_space -= discarding_size;
|
||||
m_reading_head = (m_reading_head + discarding_size) % capacity();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
67
AK/CircularBuffer.h
Normal file
67
AK/CircularBuffer.h
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Lucas Chollet <lucas.chollet@free.fr>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/Error.h>
|
||||
#include <AK/Noncopyable.h>
|
||||
|
||||
namespace AK {
|
||||
|
||||
class CircularBuffer {
|
||||
AK_MAKE_NONCOPYABLE(CircularBuffer);
|
||||
|
||||
public:
|
||||
static ErrorOr<CircularBuffer> create_empty(size_t size);
|
||||
static ErrorOr<CircularBuffer> create_initialized(ByteBuffer);
|
||||
|
||||
CircularBuffer(CircularBuffer&& other)
|
||||
{
|
||||
operator=(move(other));
|
||||
}
|
||||
|
||||
CircularBuffer& operator=(CircularBuffer&& other)
|
||||
{
|
||||
if (&other == this)
|
||||
return *this;
|
||||
|
||||
swap(m_buffer, other.m_buffer);
|
||||
swap(m_reading_head, other.m_reading_head);
|
||||
swap(m_used_space, other.m_used_space);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
~CircularBuffer() = default;
|
||||
|
||||
size_t write(ReadonlyBytes bytes);
|
||||
Bytes read(Bytes bytes);
|
||||
ErrorOr<void> discard(size_t discarded_bytes);
|
||||
|
||||
[[nodiscard]] size_t empty_space() const;
|
||||
[[nodiscard]] size_t used_space() const;
|
||||
[[nodiscard]] size_t capacity() const;
|
||||
|
||||
Optional<size_t> offset_of(StringView needle, Optional<size_t> until = {}) const;
|
||||
|
||||
void clear();
|
||||
|
||||
private:
|
||||
CircularBuffer(ByteBuffer);
|
||||
|
||||
[[nodiscard]] bool is_wrapping_around() const;
|
||||
|
||||
[[nodiscard]] Bytes next_write_span();
|
||||
[[nodiscard]] ReadonlyBytes next_read_span() const;
|
||||
|
||||
ByteBuffer m_buffer {};
|
||||
|
||||
size_t m_reading_head {};
|
||||
size_t m_used_space {};
|
||||
};
|
||||
|
||||
}
|
|
@ -18,6 +18,7 @@ class ByteBuffer;
|
|||
|
||||
class Bitmap;
|
||||
using ByteBuffer = Detail::ByteBuffer<32>;
|
||||
class CircularBuffer;
|
||||
class Error;
|
||||
class GenericLexer;
|
||||
class IPv4Address;
|
||||
|
@ -155,6 +156,7 @@ using AK::Badge;
|
|||
using AK::Bitmap;
|
||||
using AK::ByteBuffer;
|
||||
using AK::Bytes;
|
||||
using AK::CircularBuffer;
|
||||
using AK::CircularDuplexStream;
|
||||
using AK::CircularQueue;
|
||||
using AK::DeprecatedString;
|
||||
|
|
|
@ -14,6 +14,7 @@ set(AK_TEST_SOURCES
|
|||
TestByteBuffer.cpp
|
||||
TestCharacterTypes.cpp
|
||||
TestChecked.cpp
|
||||
TestCircularBuffer.cpp
|
||||
TestCircularDeque.cpp
|
||||
TestCircularDuplexStream.cpp
|
||||
TestCircularQueue.cpp
|
||||
|
|
277
Tests/AK/TestCircularBuffer.cpp
Normal file
277
Tests/AK/TestCircularBuffer.cpp
Normal file
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Lucas Chollet <lucas.chollet@free.fr>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibTest/TestCase.h>
|
||||
|
||||
#include <AK/CircularBuffer.h>
|
||||
|
||||
namespace {
|
||||
|
||||
CircularBuffer create_circular_buffer(size_t size)
|
||||
{
|
||||
auto buffer_or_error = CircularBuffer::create_empty(size);
|
||||
EXPECT(!buffer_or_error.is_error());
|
||||
|
||||
return buffer_or_error.release_value();
|
||||
}
|
||||
|
||||
void safe_write(CircularBuffer& buffer, u8 i)
|
||||
{
|
||||
Bytes b { &i, 1 };
|
||||
auto written_bytes = buffer.write(b);
|
||||
EXPECT_EQ(written_bytes, 1ul);
|
||||
};
|
||||
|
||||
void safe_read(CircularBuffer& buffer, u8 supposed_result)
|
||||
{
|
||||
u8 read_value {};
|
||||
Bytes b { &read_value, 1 };
|
||||
b = buffer.read(b);
|
||||
EXPECT_EQ(b.size(), 1ul);
|
||||
EXPECT_EQ(*b.data(), supposed_result);
|
||||
};
|
||||
|
||||
void safe_discard(CircularBuffer& buffer, size_t size)
|
||||
{
|
||||
auto result = buffer.discard(size);
|
||||
EXPECT(!result.is_error());
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE(simple_write_read)
|
||||
{
|
||||
auto buffer = create_circular_buffer(1);
|
||||
|
||||
safe_write(buffer, 42);
|
||||
safe_read(buffer, 42);
|
||||
}
|
||||
|
||||
TEST_CASE(writing_above_limits)
|
||||
{
|
||||
auto buffer = create_circular_buffer(1);
|
||||
|
||||
safe_write(buffer, 1);
|
||||
|
||||
u8 value = 42;
|
||||
Bytes b { &value, 1 };
|
||||
auto written_bytes = buffer.write(b);
|
||||
EXPECT_EQ(written_bytes, 0ul);
|
||||
}
|
||||
|
||||
TEST_CASE(usage_with_wrapping_around)
|
||||
{
|
||||
constexpr size_t capacity = 3;
|
||||
auto buffer = create_circular_buffer(capacity);
|
||||
|
||||
for (unsigned i {}; i < capacity; ++i)
|
||||
safe_write(buffer, i + 8);
|
||||
|
||||
EXPECT_EQ(buffer.used_space(), capacity);
|
||||
EXPECT_EQ(buffer.empty_space(), 0ul);
|
||||
|
||||
safe_read(buffer, 0 + 8);
|
||||
safe_read(buffer, 1 + 8);
|
||||
|
||||
EXPECT_EQ(buffer.used_space(), capacity - 2);
|
||||
|
||||
safe_write(buffer, 5);
|
||||
safe_write(buffer, 6);
|
||||
|
||||
EXPECT_EQ(buffer.used_space(), capacity);
|
||||
|
||||
safe_read(buffer, 10);
|
||||
safe_read(buffer, 5);
|
||||
safe_read(buffer, 6);
|
||||
|
||||
EXPECT_EQ(buffer.used_space(), 0ul);
|
||||
}
|
||||
|
||||
TEST_CASE(full_read_aligned)
|
||||
{
|
||||
constexpr size_t capacity = 3;
|
||||
auto buffer = create_circular_buffer(capacity);
|
||||
|
||||
for (unsigned i {}; i < capacity; ++i)
|
||||
safe_write(buffer, i);
|
||||
|
||||
EXPECT_EQ(buffer.used_space(), capacity);
|
||||
EXPECT_EQ(buffer.empty_space(), 0ul);
|
||||
|
||||
u8 const source[] = { 0, 1, 2 };
|
||||
|
||||
u8 result[] = { 0, 0, 0 };
|
||||
auto const bytes_or_error = buffer.read({ result, 3 });
|
||||
EXPECT_EQ(bytes_or_error.size(), 3ul);
|
||||
|
||||
EXPECT_EQ(memcmp(source, result, 3), 0);
|
||||
}
|
||||
|
||||
TEST_CASE(full_read_non_aligned)
|
||||
{
|
||||
constexpr size_t capacity = 3;
|
||||
auto buffer = create_circular_buffer(capacity);
|
||||
|
||||
for (unsigned i {}; i < capacity; ++i)
|
||||
safe_write(buffer, i + 5);
|
||||
|
||||
safe_read(buffer, 5);
|
||||
|
||||
safe_write(buffer, 42);
|
||||
|
||||
EXPECT_EQ(buffer.used_space(), capacity);
|
||||
EXPECT_EQ(buffer.empty_space(), 0ul);
|
||||
|
||||
u8 result[] = { 0, 0, 0 };
|
||||
auto const bytes = buffer.read({ result, 3 });
|
||||
EXPECT_EQ(bytes.size(), 3ul);
|
||||
|
||||
u8 const source[] = { 6, 7, 42 };
|
||||
EXPECT_EQ(memcmp(source, result, 3), 0);
|
||||
}
|
||||
|
||||
TEST_CASE(full_write_aligned)
|
||||
{
|
||||
constexpr size_t capacity = 3;
|
||||
auto buffer = create_circular_buffer(capacity);
|
||||
|
||||
u8 const source[] = { 12, 13, 14 };
|
||||
|
||||
auto written_bytes = buffer.write({ source, 3 });
|
||||
EXPECT_EQ(written_bytes, 3ul);
|
||||
|
||||
EXPECT_EQ(buffer.used_space(), capacity);
|
||||
EXPECT_EQ(buffer.empty_space(), 0ul);
|
||||
|
||||
for (unsigned i {}; i < capacity; ++i)
|
||||
safe_read(buffer, i + 12);
|
||||
|
||||
EXPECT_EQ(buffer.used_space(), 0ul);
|
||||
}
|
||||
|
||||
TEST_CASE(full_write_non_aligned)
|
||||
{
|
||||
constexpr size_t capacity = 3;
|
||||
auto buffer = create_circular_buffer(capacity);
|
||||
|
||||
safe_write(buffer, 10);
|
||||
safe_read(buffer, 10);
|
||||
|
||||
u8 const source[] = { 12, 13, 14 };
|
||||
|
||||
auto written_bytes = buffer.write({ source, 3 });
|
||||
EXPECT_EQ(written_bytes, 3ul);
|
||||
|
||||
EXPECT_EQ(buffer.used_space(), capacity);
|
||||
EXPECT_EQ(buffer.empty_space(), 0ul);
|
||||
|
||||
for (unsigned i {}; i < capacity; ++i)
|
||||
safe_read(buffer, i + 12);
|
||||
|
||||
EXPECT_EQ(buffer.used_space(), 0ul);
|
||||
}
|
||||
|
||||
TEST_CASE(create_from_bytebuffer)
|
||||
{
|
||||
u8 const source[] = { 2, 4, 6 };
|
||||
auto byte_buffer_or_error = ByteBuffer::copy(source, AK::array_size(source));
|
||||
EXPECT(!byte_buffer_or_error.is_error());
|
||||
auto byte_buffer = byte_buffer_or_error.release_value();
|
||||
|
||||
auto circular_buffer_or_error = CircularBuffer::create_initialized(move(byte_buffer));
|
||||
EXPECT(!circular_buffer_or_error.is_error());
|
||||
auto circular_buffer = circular_buffer_or_error.release_value();
|
||||
EXPECT_EQ(circular_buffer.used_space(), circular_buffer.capacity());
|
||||
EXPECT_EQ(circular_buffer.used_space(), 3ul);
|
||||
|
||||
safe_read(circular_buffer, 2);
|
||||
safe_read(circular_buffer, 4);
|
||||
safe_read(circular_buffer, 6);
|
||||
}
|
||||
|
||||
TEST_CASE(discard)
|
||||
{
|
||||
constexpr size_t capacity = 3;
|
||||
auto buffer = create_circular_buffer(capacity);
|
||||
|
||||
safe_write(buffer, 11);
|
||||
safe_write(buffer, 12);
|
||||
|
||||
safe_discard(buffer, 1);
|
||||
|
||||
safe_read(buffer, 12);
|
||||
|
||||
EXPECT_EQ(buffer.used_space(), 0ul);
|
||||
EXPECT_EQ(buffer.empty_space(), capacity);
|
||||
}
|
||||
|
||||
TEST_CASE(discard_on_edge)
|
||||
{
|
||||
constexpr size_t capacity = 3;
|
||||
auto buffer = create_circular_buffer(capacity);
|
||||
|
||||
safe_write(buffer, 11);
|
||||
safe_write(buffer, 12);
|
||||
safe_write(buffer, 13);
|
||||
|
||||
safe_discard(buffer, 2);
|
||||
|
||||
safe_write(buffer, 14);
|
||||
safe_write(buffer, 15);
|
||||
|
||||
safe_discard(buffer, 2);
|
||||
|
||||
safe_read(buffer, 15);
|
||||
|
||||
EXPECT_EQ(buffer.used_space(), 0ul);
|
||||
EXPECT_EQ(buffer.empty_space(), capacity);
|
||||
}
|
||||
|
||||
TEST_CASE(discard_too_much)
|
||||
{
|
||||
constexpr size_t capacity = 3;
|
||||
auto buffer = create_circular_buffer(capacity);
|
||||
|
||||
safe_write(buffer, 11);
|
||||
safe_write(buffer, 12);
|
||||
|
||||
safe_discard(buffer, 2);
|
||||
|
||||
auto result = buffer.discard(2);
|
||||
EXPECT(result.is_error());
|
||||
}
|
||||
|
||||
TEST_CASE(offset_of)
|
||||
{
|
||||
auto const source = "Well Hello Friends!"sv;
|
||||
auto byte_buffer_or_error = ByteBuffer::copy(source.bytes());
|
||||
EXPECT(!byte_buffer_or_error.is_error());
|
||||
auto byte_buffer = byte_buffer_or_error.release_value();
|
||||
|
||||
auto circular_buffer_or_error = CircularBuffer::create_initialized(byte_buffer);
|
||||
EXPECT(!circular_buffer_or_error.is_error());
|
||||
auto circular_buffer = circular_buffer_or_error.release_value();
|
||||
|
||||
auto result = circular_buffer.offset_of("Well"sv);
|
||||
EXPECT(result.has_value());
|
||||
EXPECT_EQ(result.value(), 0ul);
|
||||
|
||||
result = circular_buffer.offset_of("Hello"sv);
|
||||
EXPECT(result.has_value());
|
||||
EXPECT_EQ(result.value(), 5ul);
|
||||
|
||||
safe_discard(circular_buffer, 5);
|
||||
|
||||
auto written_bytes = circular_buffer.write(byte_buffer.span().trim(5));
|
||||
EXPECT_EQ(written_bytes, 5ul);
|
||||
|
||||
result = circular_buffer.offset_of("!Well"sv);
|
||||
EXPECT(result.has_value());
|
||||
EXPECT_EQ(result.value(), 13ul);
|
||||
|
||||
result = circular_buffer.offset_of("!Well"sv, 12);
|
||||
EXPECT(!result.has_value());
|
||||
}
|
Loading…
Reference in a new issue