mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 12:05:15 +00:00
AK: Add an abstraction over multiple disjoint buffers
DisjointChunks<T> provides a nice interface over multiple sequential Vector<T>'s, allowing the user to iterate over/index into/slice from said buffers as if they were a single contiguous buffer. To work with views on such objects, DisjointSpans<T> is provided, which has the same behaviour but does not own the underlying objects.
This commit is contained in:
parent
910de95e7a
commit
ccb53c64e9
Notes:
sideshowbarker
2024-07-18 03:58:41 +09:00
Author: https://github.com/alimpfard Commit: https://github.com/SerenityOS/serenity/commit/ccb53c64e9e Pull-request: https://github.com/SerenityOS/serenity/pull/10019
3 changed files with 430 additions and 0 deletions
348
AK/DisjointChunks.h
Normal file
348
AK/DisjointChunks.h
Normal file
|
@ -0,0 +1,348 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/Span.h>
|
||||
#include <AK/StdLibExtras.h>
|
||||
|
||||
namespace AK {
|
||||
|
||||
template<typename ChunkType, bool IsConst>
|
||||
struct DisjointIterator {
|
||||
struct EndTag {
|
||||
};
|
||||
using ReferenceType = Conditional<IsConst, AddConst<Vector<ChunkType>>, Vector<ChunkType>>&;
|
||||
|
||||
DisjointIterator(ReferenceType chunks)
|
||||
: m_chunks(chunks)
|
||||
{
|
||||
}
|
||||
|
||||
DisjointIterator(ReferenceType chunks, EndTag)
|
||||
: m_chunk_index(chunks.size())
|
||||
, m_index_in_chunk(0)
|
||||
, m_chunks(chunks)
|
||||
{
|
||||
}
|
||||
|
||||
DisjointIterator& operator++()
|
||||
{
|
||||
if (m_chunk_index >= m_chunks.size())
|
||||
return *this;
|
||||
|
||||
auto& chunk = m_chunks[m_chunk_index];
|
||||
if (m_index_in_chunk + 1 >= chunk.size()) {
|
||||
++m_chunk_index;
|
||||
m_index_in_chunk = 0;
|
||||
} else {
|
||||
++m_index_in_chunk;
|
||||
}
|
||||
if (m_chunk_index < m_chunks.size()) {
|
||||
while (m_chunks[m_chunk_index].is_empty())
|
||||
++m_chunk_index;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(DisjointIterator const& other) const
|
||||
{
|
||||
return &other.m_chunks == &m_chunks && other.m_index_in_chunk == m_index_in_chunk && other.m_chunk_index == m_chunk_index;
|
||||
}
|
||||
|
||||
auto& operator*() requires(!IsConst) { return m_chunks[m_chunk_index][m_index_in_chunk]; }
|
||||
auto* operator->() requires(!IsConst) { return &m_chunks[m_chunk_index][m_index_in_chunk]; }
|
||||
auto const& operator*() const { return m_chunks[m_chunk_index][m_index_in_chunk]; }
|
||||
auto const* operator->() const { return &m_chunks[m_chunk_index][m_index_in_chunk]; }
|
||||
|
||||
private:
|
||||
size_t m_chunk_index { 0 };
|
||||
size_t m_index_in_chunk { 0 };
|
||||
ReferenceType m_chunks;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class DisjointSpans {
|
||||
public:
|
||||
DisjointSpans() = default;
|
||||
~DisjointSpans() = default;
|
||||
DisjointSpans(DisjointSpans const&) = default;
|
||||
DisjointSpans(DisjointSpans&&) = default;
|
||||
|
||||
explicit DisjointSpans(Vector<Span<T>> spans)
|
||||
: m_spans(move(spans))
|
||||
{
|
||||
}
|
||||
|
||||
DisjointSpans& operator=(DisjointSpans&&) = default;
|
||||
DisjointSpans& operator=(DisjointSpans const&) = default;
|
||||
|
||||
bool operator==(DisjointSpans const& other) const
|
||||
{
|
||||
if (other.size() != size())
|
||||
return false;
|
||||
|
||||
auto it = begin();
|
||||
auto other_it = other.begin();
|
||||
for (; it != end(); ++it, ++other_it) {
|
||||
if (*it != *other_it)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
T& operator[](size_t index) { return at(index); }
|
||||
T const& operator[](size_t index) const { return at(index); }
|
||||
T const& at(size_t index) const { return const_cast<DisjointSpans&>(*this).at(index); }
|
||||
T& at(size_t index)
|
||||
{
|
||||
auto span_and_offset = span_around(index);
|
||||
VERIFY(span_and_offset.offset < span_and_offset.span.size());
|
||||
return span_and_offset.span.at(span_and_offset.offset);
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
size_t size = 0;
|
||||
for (auto& span : m_spans)
|
||||
size += span.size();
|
||||
return size;
|
||||
}
|
||||
|
||||
bool is_empty() const { return size() == 0; }
|
||||
|
||||
DisjointSpans slice(size_t start, size_t length) const
|
||||
{
|
||||
DisjointSpans spans;
|
||||
for (auto& span : m_spans) {
|
||||
if (length == 0)
|
||||
break;
|
||||
if (start >= span.size()) {
|
||||
start -= span.size();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto sliced_length = min(length, span.size() - start);
|
||||
spans.m_spans.append(span.slice(start, sliced_length));
|
||||
start = 0;
|
||||
length -= sliced_length;
|
||||
}
|
||||
// Make sure that we weren't asked to make a slice larger than possible.
|
||||
VERIFY(length == 0);
|
||||
return spans;
|
||||
}
|
||||
DisjointSpans slice(size_t start) const { return slice(start, size() - start); }
|
||||
DisjointSpans slice_from_end(size_t length) const { return slice(size() - length, length); }
|
||||
|
||||
DisjointIterator<Span<T>, false> begin() { return { m_spans }; }
|
||||
DisjointIterator<Span<T>, false> end() { return { m_spans, {} }; }
|
||||
DisjointIterator<Span<T>, true> begin() const { return { m_spans }; }
|
||||
DisjointIterator<Span<T>, true> end() const { return { m_spans, {} }; }
|
||||
|
||||
private:
|
||||
struct SpanAndOffset {
|
||||
Span<T>& span;
|
||||
size_t offset;
|
||||
};
|
||||
SpanAndOffset span_around(size_t index)
|
||||
{
|
||||
size_t offset = 0;
|
||||
for (auto& span : m_spans) {
|
||||
if (span.is_empty())
|
||||
continue;
|
||||
auto next_offset = span.size() + offset;
|
||||
if (next_offset <= index) {
|
||||
offset = next_offset;
|
||||
continue;
|
||||
}
|
||||
|
||||
return { span, index - offset };
|
||||
}
|
||||
|
||||
return { m_spans.last(), index - (offset - m_spans.last().size()) };
|
||||
}
|
||||
|
||||
Vector<Span<T>> m_spans;
|
||||
};
|
||||
|
||||
template<typename T, typename ChunkType = Vector<T>>
|
||||
class DisjointChunks {
|
||||
public:
|
||||
DisjointChunks() = default;
|
||||
~DisjointChunks() = default;
|
||||
DisjointChunks(DisjointChunks const&) = default;
|
||||
DisjointChunks(DisjointChunks&&) = default;
|
||||
|
||||
DisjointChunks& operator=(DisjointChunks&&) = default;
|
||||
DisjointChunks& operator=(DisjointChunks const&) = default;
|
||||
|
||||
void append(ChunkType&& chunk) { m_chunks.append(chunk); }
|
||||
void extend(DisjointChunks&& chunks) { m_chunks.extend(move(chunks.m_chunks)); }
|
||||
void extend(DisjointChunks const& chunks) { m_chunks.extend(chunks.m_chunks); }
|
||||
|
||||
ChunkType& first_chunk() { return m_chunks.first(); }
|
||||
ChunkType& last_chunk() { return m_chunks.last(); }
|
||||
ChunkType const& first_chunk() const { return m_chunks.first(); }
|
||||
ChunkType const& last_chunk() const { return m_chunks.last(); }
|
||||
|
||||
void insert(size_t index, T value)
|
||||
{
|
||||
if (m_chunks.size() == 1)
|
||||
return m_chunks.first().insert(index, value);
|
||||
auto chunk_and_offset = chunk_around(index);
|
||||
chunk_and_offset.chunk.insert(chunk_and_offset.offset, move(value));
|
||||
}
|
||||
|
||||
void clear() { m_chunks.clear(); }
|
||||
|
||||
T& operator[](size_t index) { return at(index); }
|
||||
T const& operator[](size_t index) const { return at(index); }
|
||||
T const& at(size_t index) const { return const_cast<DisjointChunks&>(*this).at(index); }
|
||||
T& at(size_t index)
|
||||
{
|
||||
if (m_chunks.size() == 1)
|
||||
return m_chunks.first().at(index);
|
||||
auto chunk_and_offset = chunk_around(index);
|
||||
VERIFY(chunk_and_offset.offset < chunk_and_offset.chunk.size());
|
||||
return chunk_and_offset.chunk.at(chunk_and_offset.offset);
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
size_t sum = 0;
|
||||
for (auto& chunk : m_chunks)
|
||||
sum += chunk.size();
|
||||
return sum;
|
||||
}
|
||||
|
||||
bool is_empty() const { return size() == 0; }
|
||||
|
||||
DisjointSpans<T> spans() const&
|
||||
{
|
||||
Vector<Span<T>> spans;
|
||||
spans.ensure_capacity(m_chunks.size());
|
||||
for (auto& chunk : m_chunks)
|
||||
spans.unchecked_append(const_cast<ChunkType&>(chunk).span());
|
||||
return DisjointSpans<T> { move(spans) };
|
||||
}
|
||||
|
||||
bool operator==(DisjointChunks const& other) const
|
||||
{
|
||||
if (other.size() != size())
|
||||
return false;
|
||||
|
||||
auto it = begin();
|
||||
auto other_it = other.begin();
|
||||
for (; it != end(); ++it, ++other_it) {
|
||||
if (*it != *other_it)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
DisjointChunks release_slice(size_t start, size_t length) & { return move(*this).slice(start, length); }
|
||||
DisjointChunks release_slice(size_t start) & { return move(*this).slice(start); }
|
||||
|
||||
DisjointChunks slice(size_t start, size_t length) &&
|
||||
{
|
||||
DisjointChunks result;
|
||||
for (auto& chunk : m_chunks) {
|
||||
if (length == 0)
|
||||
break;
|
||||
if (start >= chunk.size()) {
|
||||
start -= chunk.size();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto sliced_length = min(length, chunk.size() - start);
|
||||
if (start == 0 && sliced_length == chunk.size()) {
|
||||
// Happy path! move the chunk itself.
|
||||
result.m_chunks.append(move(chunk));
|
||||
} else {
|
||||
// Shatter the chunk, we were asked for only a part of it :(
|
||||
auto wanted_slice = chunk.span().slice(start, sliced_length);
|
||||
|
||||
ChunkType new_chunk;
|
||||
if constexpr (IsTriviallyConstructible<T>) {
|
||||
new_chunk.resize(wanted_slice.size());
|
||||
TypedTransfer<T>::move(new_chunk.data(), wanted_slice.data(), wanted_slice.size());
|
||||
} else {
|
||||
new_chunk.ensure_capacity(wanted_slice.size());
|
||||
for (auto& entry : wanted_slice)
|
||||
new_chunk.unchecked_append(move(entry));
|
||||
}
|
||||
result.m_chunks.append(move(new_chunk));
|
||||
|
||||
chunk.remove(start, sliced_length);
|
||||
}
|
||||
start = 0;
|
||||
length -= sliced_length;
|
||||
}
|
||||
|
||||
m_chunks.remove_all_matching([](auto& chunk) { return chunk.is_empty(); });
|
||||
|
||||
// Make sure that we weren't asked to make a slice larger than possible.
|
||||
VERIFY(length == 0);
|
||||
return result;
|
||||
}
|
||||
DisjointChunks slice(size_t start) && { return move(*this).slice(start, size() - start); }
|
||||
DisjointChunks slice_from_end(size_t length) && { return move(*this).slice(size() - length, length); }
|
||||
|
||||
void flatten()
|
||||
{
|
||||
if (m_chunks.is_empty())
|
||||
return;
|
||||
|
||||
auto size = this->size();
|
||||
auto& first_chunk = m_chunks.first();
|
||||
first_chunk.ensure_capacity(size);
|
||||
bool first = true;
|
||||
for (auto& chunk : m_chunks) {
|
||||
if (first) {
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
first_chunk.extend(move(chunk));
|
||||
}
|
||||
m_chunks.remove(1, m_chunks.size() - 1);
|
||||
}
|
||||
|
||||
DisjointIterator<ChunkType, false> begin() { return { m_chunks }; }
|
||||
DisjointIterator<ChunkType, false> end() { return { m_chunks, {} }; }
|
||||
DisjointIterator<ChunkType, true> begin() const { return { m_chunks }; }
|
||||
DisjointIterator<ChunkType, true> end() const { return { m_chunks, {} }; }
|
||||
|
||||
private:
|
||||
struct ChunkAndOffset {
|
||||
ChunkType& chunk;
|
||||
size_t offset;
|
||||
};
|
||||
ChunkAndOffset chunk_around(size_t index)
|
||||
{
|
||||
size_t offset = 0;
|
||||
for (auto& chunk : m_chunks) {
|
||||
if (chunk.is_empty())
|
||||
continue;
|
||||
auto next_offset = chunk.size() + offset;
|
||||
if (next_offset <= index) {
|
||||
offset = next_offset;
|
||||
continue;
|
||||
}
|
||||
|
||||
return { chunk, index - offset };
|
||||
}
|
||||
|
||||
return { m_chunks.last(), index - (offset - m_chunks.last().size()) };
|
||||
}
|
||||
|
||||
Vector<ChunkType> m_chunks;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using AK::DisjointChunks;
|
|
@ -17,6 +17,7 @@ set(AK_TEST_SOURCES
|
|||
TestCircularDuplexStream.cpp
|
||||
TestCircularQueue.cpp
|
||||
TestComplex.cpp
|
||||
TestDisjointChunks.cpp
|
||||
TestDistinctNumeric.cpp
|
||||
TestDoublyLinkedList.cpp
|
||||
TestEndian.cpp
|
||||
|
|
81
Tests/AK/TestDisjointChunks.cpp
Normal file
81
Tests/AK/TestDisjointChunks.cpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibTest/TestCase.h>
|
||||
|
||||
#include <AK/DisjointChunks.h>
|
||||
#include <AK/String.h>
|
||||
|
||||
TEST_CASE(basic)
|
||||
{
|
||||
DisjointChunks<size_t> chunks;
|
||||
chunks.append({});
|
||||
chunks.last_chunk().append(0);
|
||||
chunks.append({});
|
||||
chunks.last_chunk().append(1);
|
||||
chunks.last_chunk().append(2);
|
||||
chunks.last_chunk().append(3);
|
||||
chunks.append({});
|
||||
chunks.append({});
|
||||
chunks.last_chunk().append(4);
|
||||
|
||||
for (size_t i = 0; i < 5u; ++i)
|
||||
EXPECT_EQ(chunks.at(i), i);
|
||||
|
||||
auto it = chunks.begin();
|
||||
for (size_t i = 0; i < 5u; ++i, ++it)
|
||||
EXPECT_EQ(*it, i);
|
||||
|
||||
EXPECT_EQ(it, chunks.end());
|
||||
|
||||
DisjointChunks<size_t> new_chunks;
|
||||
new_chunks.extend(move(chunks));
|
||||
EXPECT_EQ(new_chunks.size(), 5u);
|
||||
|
||||
new_chunks.last_chunk().append(5);
|
||||
|
||||
auto cut_off_slice = new_chunks.release_slice(2, 3);
|
||||
EXPECT_EQ(new_chunks.size(), 3u);
|
||||
EXPECT_EQ(cut_off_slice.size(), 3u);
|
||||
|
||||
EXPECT_EQ(cut_off_slice[0], 2u);
|
||||
EXPECT_EQ(cut_off_slice[1], 3u);
|
||||
EXPECT_EQ(cut_off_slice[2], 4u);
|
||||
|
||||
EXPECT_EQ(new_chunks[0], 0u);
|
||||
EXPECT_EQ(new_chunks[1], 1u);
|
||||
EXPECT_EQ(new_chunks[2], 5u);
|
||||
}
|
||||
|
||||
TEST_CASE(spans)
|
||||
{
|
||||
DisjointChunks<size_t> chunks;
|
||||
chunks.append({ 0, 1, 2, 3, 4, 5 });
|
||||
chunks.append({ 6, 7, 8, 9 });
|
||||
|
||||
auto spans = chunks.spans();
|
||||
EXPECT_EQ(spans.size(), 10u);
|
||||
|
||||
auto slice = spans.slice(1, 4);
|
||||
EXPECT_EQ(slice.size(), 4u);
|
||||
EXPECT_EQ(slice[0], 1u);
|
||||
EXPECT_EQ(slice[1], 2u);
|
||||
EXPECT_EQ(slice[2], 3u);
|
||||
EXPECT_EQ(slice[3], 4u);
|
||||
|
||||
auto cross_chunk_slice = spans.slice(4, 4);
|
||||
EXPECT_EQ(cross_chunk_slice.size(), 4u);
|
||||
EXPECT_EQ(cross_chunk_slice[0], 4u);
|
||||
EXPECT_EQ(cross_chunk_slice[1], 5u);
|
||||
EXPECT_EQ(cross_chunk_slice[2], 6u);
|
||||
EXPECT_EQ(cross_chunk_slice[3], 7u);
|
||||
|
||||
auto it = cross_chunk_slice.begin();
|
||||
for (size_t i = 0; i < 4u; ++i, ++it)
|
||||
EXPECT_EQ(*it, i + 4u);
|
||||
|
||||
EXPECT_EQ(it, cross_chunk_slice.end());
|
||||
}
|
Loading…
Add table
Reference in a new issue