summaryrefslogtreecommitdiff
path: root/asl/containers/chunked_buffer.hpp
diff options
context:
space:
mode:
authorSteven Le Rouzic <steven.lerouzic@gmail.com>2025-05-26 00:47:54 +0200
committerSteven Le Rouzic <steven.lerouzic@gmail.com>2025-05-26 00:48:06 +0200
commita1db1cd9e22e77041d5f1360f1d1ccdc52b86306 (patch)
treec1cc6dc9c17885a0789028f7a55c7126f33beee7 /asl/containers/chunked_buffer.hpp
parent54b95b16629f0cd4bc30e6899e00019b3ab94012 (diff)
Implement chunked_buffer
Diffstat (limited to 'asl/containers/chunked_buffer.hpp')
-rw-r--r--asl/containers/chunked_buffer.hpp399
1 files changed, 399 insertions, 0 deletions
diff --git a/asl/containers/chunked_buffer.hpp b/asl/containers/chunked_buffer.hpp
new file mode 100644
index 0000000..3af7aa2
--- /dev/null
+++ b/asl/containers/chunked_buffer.hpp
@@ -0,0 +1,399 @@
+// Copyright 2025 Steven Le Rouzic
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+#include "asl/base/utility.hpp"
+#include "asl/base/assert.hpp"
+#include "asl/base/numeric.hpp"
+#include "asl/containers/buffer.hpp"
+#include "asl/memory/allocator.hpp"
+#include "asl/types/array.hpp"
+#include "asl/types/maybe_uninit.hpp"
+
+namespace asl
+{
+
+template<
+ is_object T,
+ isize_t kChunkSize,
+ allocator Allocator = DefaultAllocator>
+class chunked_buffer
+{
+ static_assert(kChunkSize > 0 && is_pow2(kChunkSize));
+
+ using Chunk = array<maybe_uninit<T>, kChunkSize>;
+
+ static constexpr isize_t chunk_index(isize_t i)
+ {
+ static constexpr int kChunkSizeLog2 = countr_zero(uint64_t{kChunkSize});
+ return i >> kChunkSizeLog2;
+ }
+
+ static constexpr isize_t index_in_chunk(isize_t i)
+ {
+ static constexpr isize_t kMask = kChunkSize - 1;
+ return i & kMask;
+ }
+
+ struct PerChunkIterator
+ {
+ isize_t from_chunk;
+ isize_t to_chunk;
+
+ isize_t from_index_in_chunk;
+ isize_t to_index_in_chunk;
+
+ [[nodiscard]] constexpr bool has_more() const
+ {
+ return from_chunk <= to_chunk;
+ }
+
+ void advance()
+ {
+ from_chunk += 1;
+ from_index_in_chunk = 0;
+ }
+
+ [[nodiscard]] constexpr isize_t chunk() const { return from_chunk; }
+
+ span<maybe_uninit<T>> make_span(Chunk& chunk) const
+ {
+ isize_t from = from_index_in_chunk;
+ isize_t to = (from_chunk == to_chunk) ? to_index_in_chunk : kChunkSize - 1;
+ return chunk.as_span().subspan(from, to - from + 1);
+ }
+ };
+
+ PerChunkIterator make_index_iterator(isize_t from, isize_t to)
+ {
+ return PerChunkIterator {
+ chunk_index(from), chunk_index(to),
+ index_in_chunk(from), index_in_chunk(to)
+ };
+ }
+
+ buffer<Chunk*, Allocator> m_chunks;
+ isize_t m_size{};
+
+ void resize_uninit_inner(isize_t new_size)
+ {
+ ASL_ASSERT(new_size >= 0);
+
+ if constexpr (!trivially_destructible<T>)
+ {
+ const isize_t old_size = size();
+ if (new_size < old_size)
+ {
+ for (PerChunkIterator it = make_index_iterator(new_size, old_size - 1);
+ it.has_more();
+ it.advance())
+ {
+ auto span = it.make_span(*m_chunks[it.chunk()]);
+ for (auto& el: span)
+ {
+ el.destroy_unsafe();
+ }
+ }
+ }
+ }
+
+ reserve_capacity(new_size);
+ m_size = new_size;
+ }
+
+ template<typename... Args>
+ void resize_construct(isize_t new_size, Args&&... args)
+ requires constructible_from<T, Args&&...>
+ {
+ const isize_t old_size = m_size;
+ resize_uninit_inner(new_size);
+
+ if (new_size > old_size)
+ {
+ for (PerChunkIterator it = make_index_iterator(old_size, new_size - 1);
+ it.has_more();
+ it.advance())
+ {
+ auto span = it.make_span(*m_chunks[it.chunk()]);
+ for (auto& uninit: span)
+ {
+ uninit.construct_unsafe(std::forward<Args>(args)...);
+ }
+ }
+ }
+ }
+
+ void copy_from(const chunked_buffer& other)
+ requires copyable<T>
+ {
+ const isize_t this_size = size();
+ isize_t to_copy_assign = asl::min(other.size(), this_size);
+
+ resize_uninit_inner(other.size());
+
+ for (PerChunkIterator it = make_index_iterator(0, to_copy_assign - 1);
+ it.has_more();
+ it.advance())
+ {
+ auto to_span = it.make_span(*m_chunks[it.chunk()]);
+ auto from_span = it.make_span(*other.m_chunks[it.chunk()]);
+
+ copy_assign_n(
+ reinterpret_cast<T*>(to_span.data()), // NOLINT(*-reinterpret-cast)
+ reinterpret_cast<const T*>(from_span.data()), // NOLINT(*-reinterpret-cast)
+ to_span.size());
+ }
+
+ if (other.size() > this_size)
+ {
+ for (PerChunkIterator it = make_index_iterator(to_copy_assign, other.size() - 1);
+ it.has_more();
+ it.advance())
+ {
+ auto to_span = it.make_span(*m_chunks[it.chunk()]);
+ auto from_span = it.make_span(*other.m_chunks[it.chunk()]);
+
+ copy_uninit_n(
+ reinterpret_cast<T*>(to_span.data()), // NOLINT(*-reinterpret-cast)
+ reinterpret_cast<const T*>(from_span.data()), // NOLINT(*-reinterpret-cast)
+ to_span.size());
+ }
+ }
+
+ ASL_ASSERT(size() == other.size());
+ }
+
+public:
+ constexpr chunked_buffer()
+ requires default_constructible<Allocator>
+ = default;
+
+ explicit constexpr chunked_buffer(Allocator allocator)
+ : m_chunks{std::move(allocator)}
+ {}
+
+ constexpr chunked_buffer(const chunked_buffer& other)
+ requires copyable<T> && copy_constructible<Allocator>
+ : m_chunks{other.m_chunks.allocator_copy()}
+ {
+ copy_from(other);
+ }
+
+ constexpr chunked_buffer(chunked_buffer&& other)
+ : m_chunks{std::move(other.m_chunks)}
+ , m_size{asl::exchange(other.m_size, 0)}
+ {
+ ASL_ASSERT(other.m_chunks.size() == 0);
+ }
+
+ constexpr chunked_buffer& operator=(const chunked_buffer& other)
+ requires copyable<T>
+ {
+ if (&other == this) { return *this; }
+ copy_from(other);
+ return *this;
+ }
+
+ constexpr chunked_buffer& operator=(chunked_buffer&& other)
+ {
+ if (&other == this) { return *this; }
+ destroy();
+ m_chunks = std::move(other.m_chunks);
+ m_size = asl::exchange(other.m_size, 0);
+ ASL_ASSERT(other.m_chunks.size() == 0);
+ return *this;
+ }
+
+ ~chunked_buffer()
+ {
+ destroy();
+ }
+
+ void clear()
+ {
+ if constexpr (trivially_destructible<T>)
+ {
+ m_size = 0;
+ }
+ else if (m_size > 0)
+ {
+ resize_uninit_inner(0);
+ ASL_ASSERT(m_size == 0);
+ }
+ }
+
+ void destroy()
+ {
+ clear();
+ ASL_ASSERT(size() == 0);
+
+ for (Chunk* chunk: m_chunks)
+ {
+ alloc_delete(m_chunks.allocator(), chunk);
+ }
+
+ m_chunks.destroy();
+ }
+
+ [[nodiscard]] constexpr isize_t size() const { return m_size; }
+
+ [[nodiscard]] constexpr bool is_empty() const { return size() == 0; }
+
+ [[nodiscard]] constexpr isize_t capacity() const
+ {
+ return m_chunks.size() * kChunkSize;
+ }
+
+ constexpr auto&& operator[](this auto&& self, isize_t i)
+ {
+ ASL_ASSERT(i >= 0 && i < self.m_size);
+ return std::forward_like<decltype(self)>(
+ (*std::forward<decltype(self)>(self).m_chunks[chunk_index(i)])
+ [index_in_chunk(i)].as_init_unsafe()
+ );
+ }
+
+ constexpr T& push(auto&&... args)
+ requires constructible_from<T, decltype(args)&&...>
+ {
+ const isize_t chunk = chunk_index(m_size);
+ const isize_t in_chunk = index_in_chunk(m_size);
+
+ if (m_size == capacity())
+ {
+ resize_uninit_inner(m_size + 1);
+ }
+ else
+ {
+ m_size += 1;
+ }
+
+ void* uninit = &(*m_chunks[chunk])[in_chunk];
+ return *construct_at<T>(uninit, std::forward<decltype(args)>(args)...);
+ }
+
+ void reserve_capacity(isize_t new_capacity)
+ {
+ new_capacity = round_up_pow2(new_capacity, kChunkSize);
+ if (new_capacity <= capacity()) { return; }
+
+ const isize_t required_chunks = new_capacity / kChunkSize;
+ const isize_t additional_chunks = required_chunks - m_chunks.size();
+ ASL_ASSERT(additional_chunks > 0);
+
+ m_chunks.reserve_capacity(required_chunks);
+ for (isize_t i = 0; i < additional_chunks; ++i)
+ {
+ // @Todo(C++26) _unsafe shouldn't be needed with trivial unions
+ auto* chunk = alloc_uninit_unsafe<Chunk>(m_chunks.allocator());
+ m_chunks.push(chunk);
+ }
+ }
+
+ void resize(isize_t new_size)
+ requires default_constructible<T>
+ {
+ if constexpr (trivially_default_constructible<T>)
+ {
+ resize_zero(new_size);
+ }
+ else
+ {
+ resize_construct(new_size);
+ }
+ }
+
+ void resize(isize_t new_size, const T& value)
+ requires copy_constructible<T>
+ {
+ resize_construct(new_size, value);
+ }
+
+ void resize_zero(isize_t new_size)
+ requires trivially_default_constructible<T>
+ {
+ const isize_t old_size = m_size;
+ resize_uninit_inner(new_size);
+
+ if (new_size > old_size)
+ {
+ for (PerChunkIterator it = make_index_iterator(old_size, new_size - 1);
+ it.has_more();
+ it.advance())
+ {
+ auto span = it.make_span(*m_chunks[it.chunk()]);
+ asl::memzero(span.data(), span.size_bytes());
+ }
+ }
+ }
+
+ void resize_uninit(isize_t new_size)
+ requires trivially_default_constructible<T>
+ {
+ resize_uninit_inner(new_size);
+ }
+
+ template<typename Chunk>
+ class generic_iterator
+ {
+ isize_t m_index;
+ span<Chunk> m_chunks;
+
+ public:
+ constexpr generic_iterator(isize_t index, span<Chunk> chunks)
+ : m_index{index}
+ , m_chunks{chunks}
+ {}
+
+ constexpr generic_iterator& operator++()
+ {
+ m_index += 1;
+ return *this;
+ }
+
+ constexpr generic_iterator operator++(int)
+ {
+ auto tmp = *this;
+ m_index += 1;
+ return tmp;
+ }
+
+ constexpr bool operator==(this generic_iterator self, generic_iterator other)
+ {
+ ASL_ASSERT(self.m_chunks.data() == other.m_chunks.data());
+ return self.m_index == other.m_index;
+ }
+
+ constexpr auto& operator*(this generic_iterator self)
+ {
+ ASL_ASSERT(self.m_index >= 0);
+ return (*self.m_chunks[chunk_index(self.m_index)])[index_in_chunk(self.m_index)].as_init_unsafe();
+ }
+
+ constexpr auto* operator->(this generic_iterator self)
+ {
+ return &*self;
+ }
+ };
+
+ using iterator = generic_iterator<Chunk*>;
+ using const_iterator = generic_iterator<const Chunk* const>;
+
+ constexpr iterator begin() { return iterator{0, m_chunks}; }
+ constexpr iterator end() { return iterator{m_size, m_chunks}; }
+
+ constexpr const_iterator begin() const
+ {
+ return const_iterator{0, {m_chunks.data(), m_chunks.size()}};
+ }
+
+ constexpr const_iterator end() const
+ {
+ return const_iterator{m_size, {m_chunks.data(), m_chunks.size()}};
+ }
+};
+
+} // namespace asl
+