Add SparseHandlePool
This commit is contained in:
@ -32,6 +32,17 @@ cc_library(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "sparse_handle_pool",
|
||||
hdrs = [
|
||||
"sparse_handle_pool.hpp",
|
||||
],
|
||||
deps = [
|
||||
":index_pool",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "index_pool_tests",
|
||||
srcs = [
|
||||
@ -56,3 +67,15 @@ cc_test(
|
||||
"//asl/testing",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "sparse_handle_pool_tests",
|
||||
srcs = [
|
||||
"sparse_handle_pool_tests.cpp",
|
||||
],
|
||||
deps = [
|
||||
":sparse_handle_pool",
|
||||
"//asl/tests:utils",
|
||||
"//asl/testing",
|
||||
],
|
||||
)
|
||||
|
158
asl/handle_pool/sparse_handle_pool.hpp
Normal file
158
asl/handle_pool/sparse_handle_pool.hpp
Normal file
@ -0,0 +1,158 @@
|
||||
// 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 copyable<Allocator>
|
||||
class SparseHandlePool
|
||||
{
|
||||
using ThisIndexPool = IndexPool<kIndexBits, kGenBits, UserType, kUserBits, empty, Allocator>;
|
||||
|
||||
struct Slot
|
||||
{
|
||||
ThisIndexPool::handle h;
|
||||
maybe_uninit<T> obj;
|
||||
|
||||
Slot() = default;
|
||||
ASL_DELETE_COPY_MOVE(Slot);
|
||||
|
||||
~Slot()
|
||||
{
|
||||
if (!h.is_null())
|
||||
{
|
||||
obj.destroy_unsafe();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using Buffer = chunked_buffer<Slot, kChunkSize, Allocator>;
|
||||
|
||||
ThisIndexPool m_index_pool{};
|
||||
Buffer m_buffer{};
|
||||
|
||||
using config = ThisIndexPool::handle::config;
|
||||
|
||||
template<typename... Args>
|
||||
void set_object(ThisIndexPool::handle h, Args&&... args)
|
||||
{
|
||||
const auto index = static_cast<isize_t>(h.index());
|
||||
if (m_buffer.size() <= index)
|
||||
{
|
||||
m_buffer.resize(index + 1);
|
||||
}
|
||||
m_buffer[index].h = h;
|
||||
m_buffer[index].obj.construct_unsafe(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
public:
|
||||
using handle = ThisIndexPool::handle;
|
||||
|
||||
SparseHandlePool() requires default_constructible<Allocator> = default;
|
||||
|
||||
explicit SparseHandlePool(const Allocator& allocator)
|
||||
: m_index_pool(allocator)
|
||||
, m_buffer(allocator)
|
||||
{}
|
||||
|
||||
ASL_DELETE_COPY(SparseHandlePool);
|
||||
ASL_DEFAULT_MOVE(SparseHandlePool);
|
||||
~SparseHandlePool() = 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 auto handle = m_index_pool.acquire_ensure(user);
|
||||
set_object(handle, std::forward<Args>(args)...);
|
||||
return handle;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
option<handle> acquire(Args&&... args)
|
||||
requires (!config::kHasUser) && constructible_from<T, Args&&...>
|
||||
{
|
||||
if (is_full()) { return nullopt; }
|
||||
const auto handle = m_index_pool.acquire_ensure();
|
||||
set_object(handle, std::forward<Args>(args)...);
|
||||
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 auto handle = m_index_pool.acquire_ensure(user);
|
||||
set_object(handle, std::forward<Args>(args)...);
|
||||
return handle;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
handle acquire_ensure(Args&&... args)
|
||||
requires (!config::kHasUser) && constructible_from<T, Args&&...>
|
||||
{
|
||||
ASL_ASSERT_RELEASE(!is_full());
|
||||
const auto handle = m_index_pool.acquire_ensure();
|
||||
set_object(handle, std::forward<Args>(args)...);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void release(handle h)
|
||||
{
|
||||
if (!is_valid(h)) { return; }
|
||||
auto& slot = m_buffer[static_cast<isize_t>(h.index())];
|
||||
slot.h = {};
|
||||
slot.obj.destroy_unsafe();
|
||||
m_index_pool.release(h);
|
||||
}
|
||||
|
||||
auto get(this auto&& self, handle h)
|
||||
-> copy_const_t<un_ref_t<decltype(self)>, T>*
|
||||
{
|
||||
if (!self.is_valid(h)) { return nullptr; }
|
||||
return &std::forward<decltype(self)>(self).m_buffer[static_cast<isize_t>(h.index())]
|
||||
.obj.as_init_unsafe();
|
||||
}
|
||||
|
||||
auto get_ensure(this auto&& self, handle h)
|
||||
-> copy_cref_t<decltype(self), T>
|
||||
{
|
||||
ASL_ASSERT_RELEASE(self.is_valid(h));
|
||||
return std::forward<decltype(self)>(self).m_buffer[static_cast<isize_t>(h.index())]
|
||||
.obj.as_init_unsafe();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace asl
|
117
asl/handle_pool/sparse_handle_pool_tests.cpp
Normal file
117
asl/handle_pool/sparse_handle_pool_tests.cpp
Normal file
@ -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/sparse_handle_pool.hpp"
|
||||
|
||||
ASL_TEST(acquire_release) // NOLINT
|
||||
{
|
||||
asl::SparseHandlePool<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::SparseHandlePool<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::SparseHandlePool<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::SparseHandlePool<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);
|
||||
}
|
Reference in New Issue
Block a user