summaryrefslogtreecommitdiff
path: root/asl
diff options
context:
space:
mode:
authorSteven Le Rouzic <steven.lerouzic@gmail.com>2025-06-26 07:51:36 +0200
committerSteven Le Rouzic <steven.lerouzic@gmail.com>2025-07-03 18:34:45 +0200
commita78e24e9ebf24fb9980328b4103c8351bae23ff3 (patch)
tree9679460d4e0cf7516737aaaefd5f3736931b73f6 /asl
parent43ab95880d54319ed8c02133c11392b37557ce58 (diff)
Add DenseHandlePool
Diffstat (limited to 'asl')
-rw-r--r--asl/handle_pool/BUILD.bazel23
-rw-r--r--asl/handle_pool/dense_handle_pool.hpp162
-rw-r--r--asl/handle_pool/dense_handle_pool_tests.cpp117
3 files changed, 302 insertions, 0 deletions
diff --git a/asl/handle_pool/BUILD.bazel b/asl/handle_pool/BUILD.bazel
index bdaa711..08a41a9 100644
--- a/asl/handle_pool/BUILD.bazel
+++ b/asl/handle_pool/BUILD.bazel
@@ -21,6 +21,17 @@ cc_library(
visibility = ["//visibility:public"],
)
+cc_library(
+ name = "dense_handle_pool",
+ hdrs = [
+ "dense_handle_pool.hpp",
+ ],
+ deps = [
+ ":index_pool",
+ ],
+ visibility = ["//visibility:public"],
+)
+
cc_test(
name = "index_pool_tests",
srcs = [
@@ -33,3 +44,15 @@ cc_test(
"//asl/testing",
],
)
+
+cc_test(
+ name = "dense_handle_pool_tests",
+ srcs = [
+ "dense_handle_pool_tests.cpp",
+ ],
+ deps = [
+ ":dense_handle_pool",
+ "//asl/tests:utils",
+ "//asl/testing",
+ ],
+)
diff --git a/asl/handle_pool/dense_handle_pool.hpp b/asl/handle_pool/dense_handle_pool.hpp
new file mode 100644
index 0000000..34cd04f
--- /dev/null
+++ b/asl/handle_pool/dense_handle_pool.hpp
@@ -0,0 +1,162 @@
+// Copyright 2025 Steven Le Rouzic
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+#include "asl/handle_pool/index_pool.hpp"
+#include "asl/memory/allocator.hpp"
+#include "asl/containers/chunked_buffer.hpp"
+
+
+namespace asl
+{
+
+// @Todo If we want the allocator to be non-copyable, we could
+// introduce a reference allocator type that is copyable, and store
+// the "main" allocator in the pool.
+
+template<
+ is_object T,
+ int kIndexBits,
+ int kGenBits,
+ typename UserType = empty,
+ int kUserBits = 0,
+ isize_t kChunkSize = 32,
+ allocator Allocator = DefaultAllocator>
+requires moveable<T> && copyable<Allocator>
+class DenseHandlePool
+{
+ using ThisIndexPool = IndexPool<kIndexBits, kGenBits, UserType, kUserBits, isize_t, Allocator>;
+
+ struct Slot
+ {
+ ThisIndexPool::handle h;
+ T obj;
+
+ template<typename... Args>
+ explicit Slot(ThisIndexPool::handle h, Args&&... args)
+ : h{h}
+ , obj(std::forward<Args>(args)...)
+ {}
+ };
+
+ using Buffer = chunked_buffer<Slot, kChunkSize, Allocator>;
+
+ ThisIndexPool m_index_pool{};
+ Buffer m_buffer{};
+
+ using config = ThisIndexPool::handle::config;
+
+ template<typename... Args>
+ isize_t push(Args&&... args)
+ requires constructible_from<T, Args&&...>
+ {
+ m_buffer.push(typename ThisIndexPool::handle{}, std::forward<Args>(args)...);
+ return m_buffer.size() - 1;
+ }
+
+public:
+ using handle = ThisIndexPool::handle;
+
+ DenseHandlePool() requires default_constructible<Allocator> = default;
+
+ explicit DenseHandlePool(const Allocator& allocator)
+ : m_index_pool(allocator)
+ , m_buffer(allocator)
+ {}
+
+ ASL_DELETE_COPY(DenseHandlePool);
+ ASL_DEFAULT_MOVE(DenseHandlePool);
+ ~DenseHandlePool() = default;
+
+ [[nodiscard]] bool is_full() const
+ {
+ return m_index_pool.is_full();
+ }
+
+ bool is_valid(handle h) const
+ {
+ return m_index_pool.is_valid(h);
+ }
+
+ template<typename... Args>
+ option<handle> acquire(config::UserType user, Args&&... args)
+ requires config::kHasUser && constructible_from<T, Args&&...>
+ {
+ if (is_full()) { return nullopt; }
+ const isize_t obj_index = push(std::forward<Args>(args)...);
+ const auto handle = m_index_pool.acquire_ensure(user, obj_index);
+ m_buffer[obj_index].h = handle;
+ return handle;
+ }
+
+ template<typename... Args>
+ option<handle> acquire(Args&&... args)
+ requires (!config::kHasUser) && constructible_from<T, Args&&...>
+ {
+ if (is_full()) { return nullopt; }
+ const isize_t obj_index = push(std::forward<Args>(args)...);
+ const auto handle = m_index_pool.acquire_ensure(obj_index);
+ m_buffer[obj_index].h = handle;
+ return handle;
+ }
+
+ template<typename... Args>
+ handle acquire_ensure(config::UserType user, Args&&... args)
+ requires config::kHasUser && constructible_from<T, Args&&...>
+ {
+ ASL_ASSERT_RELEASE(!is_full());
+ const isize_t obj_index = push(std::forward<Args>(args)...);
+ const auto handle = m_index_pool.acquire_ensure(user, obj_index);
+ m_buffer[obj_index].h = handle;
+ return handle;
+ }
+
+ template<typename... Args>
+ handle acquire_ensure(Args&&... args)
+ requires (!config::kHasUser) && constructible_from<T, Args&&...>
+ {
+ ASL_ASSERT_RELEASE(!is_full());
+ const isize_t obj_index = push(std::forward<Args>(args)...);
+ const auto handle = m_index_pool.acquire_ensure(obj_index);
+ m_buffer[obj_index].h = handle;
+ return handle;
+ }
+
+ void release(handle to_release_handle)
+ {
+ if (!is_valid(to_release_handle)) { return; }
+
+ const auto to_release_index = *m_index_pool.get_payload(to_release_handle);
+ if (to_release_index < m_buffer.size() - 1)
+ {
+ const auto to_swap_index = m_buffer.size() - 1;
+ const auto to_swap_handle = m_buffer[to_swap_index].h;
+
+ m_buffer[to_release_index] = std::move(m_buffer[to_swap_index]);
+ m_index_pool.exchange_payload(to_swap_handle, to_release_index);
+ }
+
+ m_buffer.pop();
+ m_index_pool.release(to_release_handle);
+ }
+
+ auto get(this auto&& self, handle h)
+ -> copy_const_t<un_ref_t<decltype(self)>, T>*
+ {
+ if (!self.is_valid(h)) { return nullptr; }
+ const auto index = *self.m_index_pool.get_payload(h);
+ return &self.m_buffer[index].obj;
+ }
+
+ auto get_ensure(this auto&& self, handle h)
+ -> copy_cref_t<decltype(self), T>
+ {
+ ASL_ASSERT_RELEASE(self.is_valid(h));
+ const auto index = *self.m_index_pool.get_payload(h);
+ return std::forward<decltype(self)>(self).m_buffer[index].obj;
+ }
+};
+
+} // namespace asl
diff --git a/asl/handle_pool/dense_handle_pool_tests.cpp b/asl/handle_pool/dense_handle_pool_tests.cpp
new file mode 100644
index 0000000..a98d1d8
--- /dev/null
+++ b/asl/handle_pool/dense_handle_pool_tests.cpp
@@ -0,0 +1,117 @@
+// Copyright 2025 Steven Le Rouzic
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "asl/testing/testing.hpp"
+#include "asl/tests/types.hpp"
+#include "asl/handle_pool/dense_handle_pool.hpp"
+
+ASL_TEST(acquire_release) // NOLINT
+{
+ asl::DenseHandlePool<int, 1, 1> pool;
+
+ ASL_TEST_EXPECT(!pool.is_full());
+
+ const auto a = pool.acquire_ensure(6);
+ const auto b = pool.acquire_ensure(7);
+
+ ASL_TEST_EXPECT(pool.is_valid(a));
+ ASL_TEST_EXPECT(pool.is_valid(b));
+ ASL_TEST_EXPECT(pool.is_full());
+
+ ASL_TEST_EXPECT(pool.get_ensure(a) == 6);
+ ASL_TEST_EXPECT(pool.get_ensure(b) == 7);
+
+ pool.release(a);
+ ASL_TEST_EXPECT(!pool.is_valid(a));
+ ASL_TEST_EXPECT(pool.is_valid(b));
+ ASL_TEST_EXPECT(!pool.is_full());
+ ASL_TEST_EXPECT(pool.get(a) == nullptr);
+ ASL_TEST_EXPECT(*pool.get(b) == 7);
+
+ const auto c = pool.acquire_ensure(8);
+
+ ASL_TEST_EXPECT(!pool.is_valid(a));
+ ASL_TEST_EXPECT(pool.is_valid(b));
+ ASL_TEST_EXPECT(pool.is_valid(c));
+ ASL_TEST_EXPECT(pool.is_full());
+
+ ASL_TEST_EXPECT(*pool.get(b) == 7);
+ ASL_TEST_EXPECT(*pool.get(c) == 8);
+ ASL_TEST_EXPECT(pool.get(a) == nullptr);
+}
+
+ASL_TEST(element_destructor)
+{
+ asl::DenseHandlePool<DestructorObserver, 8, 8> pool;
+ bool d[3]{};
+
+ const auto d0 = pool.acquire_ensure(&d[0]);
+ const auto d1 = pool.acquire_ensure(&d[1]);
+ const auto d2 = pool.acquire_ensure(&d[2]);
+
+ ASL_TEST_EXPECT(!d[0]);
+ ASL_TEST_EXPECT(!d[1]);
+ ASL_TEST_EXPECT(!d[2]);
+
+ pool.release(d1);
+ ASL_TEST_EXPECT(!d[0]);
+ ASL_TEST_EXPECT(d[1]);
+ ASL_TEST_EXPECT(!d[2]);
+
+ pool.release(d0);
+ ASL_TEST_EXPECT(d[0]);
+ ASL_TEST_EXPECT(d[1]);
+ ASL_TEST_EXPECT(!d[2]);
+
+ pool.release(d2);
+ ASL_TEST_EXPECT(d[0]);
+ ASL_TEST_EXPECT(d[1]);
+ ASL_TEST_EXPECT(d[2]);
+}
+
+ASL_TEST(destructor)
+{
+ bool d[3]{};
+
+ {
+ asl::DenseHandlePool<DestructorObserver, 8, 8> pool;
+
+ pool.acquire_ensure(&d[0]);
+ const auto d1 = pool.acquire_ensure(&d[1]);
+ pool.acquire_ensure(&d[2]);
+
+ ASL_TEST_EXPECT(!d[0]);
+ ASL_TEST_EXPECT(!d[1]);
+ ASL_TEST_EXPECT(!d[2]);
+
+ pool.release(d1);
+ ASL_TEST_EXPECT(!d[0]);
+ ASL_TEST_EXPECT(d[1]);
+ ASL_TEST_EXPECT(!d[2]);
+ }
+
+ ASL_TEST_EXPECT(d[0]);
+ ASL_TEST_EXPECT(d[1]);
+ ASL_TEST_EXPECT(d[2]);
+}
+
+enum Flags : uint8_t
+{
+ kFlag1 = 1,
+ kFlag2 = 2,
+};
+
+ASL_TEST(user_type)
+{
+ asl::DenseHandlePool<int, 8, 8, Flags> pool;
+
+ auto a = pool.acquire_ensure(kFlag2, 22);
+ auto b = pool.acquire_ensure(kFlag1, 11);
+
+ ASL_TEST_EXPECT(pool.get_ensure(a) == 22);
+ ASL_TEST_EXPECT(pool.get_ensure(b) == 11);
+
+ ASL_TEST_EXPECT(a.user() == kFlag2);
+ ASL_TEST_EXPECT(b.user() == kFlag1);
+}