summaryrefslogtreecommitdiff
path: root/asl/handle_pool/index_pool.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'asl/handle_pool/index_pool.hpp')
-rw-r--r--asl/handle_pool/index_pool.hpp264
1 files changed, 257 insertions, 7 deletions
diff --git a/asl/handle_pool/index_pool.hpp b/asl/handle_pool/index_pool.hpp
index 1d359d5..2e9ea0f 100644
--- a/asl/handle_pool/index_pool.hpp
+++ b/asl/handle_pool/index_pool.hpp
@@ -6,12 +6,11 @@
#include "asl/base/meta.hpp"
#include "asl/containers/chunked_buffer.hpp"
#include "asl/memory/allocator.hpp"
+#include "asl/types/option.hpp"
namespace asl
{
-// @Todo Uniquely represented for the handle?
-
template<
int kIndexBits_,
int kGenBits_,
@@ -27,6 +26,7 @@ struct index_pool_config
using PrimitiveUserType = smallest_unsigned_integer_type_for_width<size_of<UserType> * 8>;
static_assert(trivially_copy_constructible<UserType>);
+ static_assert(trivially_destructible<UserType>);
static_assert(size_of<UserType> == size_of<PrimitiveUserType>, "UserType should be of size 1, 2 or 4");
static constexpr int kUserBits = []() static -> int {
@@ -43,13 +43,17 @@ struct index_pool_config
using HandleType = smallest_unsigned_integer_type_for_width<kIndexBits + kGenBits + kUserBits + 1>;
- static constexpr int kGenShift = kIndexBits;
- static constexpr int kUserShift = kIndexBits + kGenBits;
+ static constexpr int kGenShift = kIndexBits;
+ static constexpr int kUserShift = kIndexBits + kGenBits;
static constexpr HandleType kValidMask = HandleType{1} << (size_of<HandleType> * 8 - 1);
static constexpr HandleType kIndexMask = (HandleType{1} << kIndexBits) - 1;
static constexpr HandleType kGenMask = ((HandleType{1} << kGenBits) - 1) << kGenShift;
static constexpr HandleType kUserMask = ((HandleType{1} << kUserBits) - 1) << kUserShift;
+ static constexpr HandleType kNiche = static_cast<HandleType>(~uint64_t{kValidMask});
+
+ static constexpr uint64_t kMaxGen = (uint64_t{1} << kGenBits) - 1;
+ static constexpr uint64_t kMaxIndex = (uint64_t{1} << kIndexBits) - 1;
};
template<
@@ -60,14 +64,19 @@ template<
>
class index_pool_handle
{
- // using config = index_pool_config<5, 5>;
+public:
using config = index_pool_config<kIndexBits_, kGenBits_, UserType_, kUserBits_>;
+private:
config::HandleType m_handle{};
public:
constexpr index_pool_handle() = default;
+ constexpr explicit index_pool_handle(niche_t)
+ : m_handle{config::kNiche}
+ {}
+
constexpr index_pool_handle(uint64_t index, uint64_t gen)
requires (!config::kHasUser)
: m_handle{static_cast<config::HandleType>(
@@ -92,9 +101,9 @@ public:
ASL_ASSERT((bit_cast<typename config::PrimitiveUserType>(user) & (uint64_t{config::kUserMask} >> config::kUserShift)) == bit_cast<typename config::PrimitiveUserType>(user));
}
- constexpr bool is_valid(this index_pool_handle self)
+ constexpr bool is_null(this index_pool_handle self)
{
- return self.m_handle & config::kValidMask;
+ return !(self.m_handle & config::kValidMask);
}
constexpr uint64_t index(this index_pool_handle self)
@@ -114,6 +123,247 @@ public:
}
constexpr bool operator==(this index_pool_handle self, index_pool_handle other) = default;
+
+ constexpr bool operator==(this index_pool_handle self, niche_t)
+ {
+ return self.m_handle == config::kNiche;
+ }
+};
+
+template<
+ int kIndexBits_,
+ int kGenBits_,
+ typename UserType_,
+ int kUserBits_
+>
+struct is_uniquely_represented<index_pool_handle<kIndexBits_, kGenBits_, UserType_, kUserBits_>> : true_type {};
+
+template<
+ int kIndexBits_,
+ int kGenBits_,
+ typename UserType_ = empty,
+ int kUserBits_ = 0,
+ typename Payload = empty,
+ allocator Allocator = DefaultAllocator
+>
+class IndexPool
+{
+public:
+ using handle = index_pool_handle<kIndexBits_, kGenBits_, UserType_, kUserBits_>;
+
+private:
+ using config = handle::config;
+
+ using internal_handle = index_pool_handle<
+ kIndexBits_,
+ kGenBits_,
+ typename config::PrimitiveUserType,
+ kUserBits_>;
+
+ static constexpr bool kHasPayload = !same_as<Payload, empty>;
+
+ // @Todo Remove need for default constructible & trivially destructible for payload
+ // Use maybe_uninit for it
+
+ static_assert(default_constructible<Payload>);
+ static_assert(copy_constructible<Payload>);
+ static_assert(trivially_destructible<Payload>);
+
+ struct Slot
+ {
+ bool is_end_of_list : 1;
+ bool is_active : 1;
+
+ internal_handle handle;
+
+ ASL_NO_UNIQUE_ADDRESS Payload payload;
+ };
+
+ chunked_buffer<Slot, 256, Allocator> m_slots;
+
+ // We only use the index, this is essentially the head of the linked
+ // list to the first available slot.
+ // Then the index of each slot points to the next available one.
+ internal_handle m_first_available;
+
+ void allocate_new_slot()
+ {
+ const auto new_index = static_cast<uint64_t>(m_slots.size());
+ if (new_index > config::kMaxIndex) { return; }
+
+ const internal_handle new_handle = internal_handle(new_index, 0, 0);
+
+ m_slots.push(Slot{
+ .is_end_of_list = true,
+ .is_active = false,
+ .handle = new_handle,
+ .payload = Payload{}
+ });
+
+ m_first_available = new_handle;
+ }
+
+ option<internal_handle> acquire_handle(const Payload& payload)
+ {
+ if (m_first_available.is_null())
+ {
+ allocate_new_slot();
+ }
+
+ if (m_first_available.is_null())
+ {
+ return nullopt;
+ }
+
+ auto index = m_first_available.index();
+
+ Slot& slot = m_slots[static_cast<isize_t>(index)];
+ ASL_ASSERT(!slot.is_active);
+
+ m_first_available = slot.is_end_of_list ? internal_handle{} : slot.handle;
+
+ slot.is_active = true;
+ slot.payload = payload;
+
+ return internal_handle(index, slot.handle.gen(), 0);
+ }
+
+ auto get_slot_if_valid(this auto&& self, handle h)
+ -> copy_const_t<decltype(self), Slot>*
+ {
+ if (h.is_null()) { return nullptr; }
+
+ auto index = static_cast<isize_t>(h.index());
+ if (index < 0 || index >= self.m_slots.size()) { return nullptr; }
+
+ auto& slot = self.m_slots[index];
+ if (!slot.is_active || slot.handle.gen() != h.gen())
+ {
+ return nullptr;
+ }
+
+ return &slot;
+ }
+
+public:
+ IndexPool() requires default_constructible<Allocator> = default;
+
+ explicit IndexPool(Allocator allocator) : m_slots{std::move(allocator)} {}
+
+ option<handle> acquire()
+ requires (!kHasPayload && !config::kHasUser)
+ {
+ return acquire_handle({}).transform([](internal_handle h)
+ {
+ return handle(h.index(), h.gen());
+ });
+ }
+
+ handle acquire_ensure()
+ requires (!kHasPayload && !config::kHasUser)
+ {
+ auto opt = acquire();
+ ASL_ASSERT_RELEASE(opt.has_value());
+ return opt.value();
+ }
+
+ option<handle> acquire(const Payload& payload)
+ requires (kHasPayload && !config::kHasUser)
+ {
+ return acquire_handle(payload).transform([](internal_handle h)
+ {
+ return handle(h.index(), h.gen());
+ });
+ }
+
+ handle acquire_ensure(const Payload& payload)
+ requires (kHasPayload && !config::kHasUser)
+ {
+ auto opt = acquire(payload);
+ ASL_ASSERT_RELEASE(opt.has_value());
+ return opt.value();
+ }
+
+ option<handle> acquire(config::UserType user)
+ requires (!kHasPayload && config::kHasUser)
+ {
+ return acquire_handle({}).transform([user](internal_handle h)
+ {
+ return handle(h.index(), h.gen(), user);
+ });
+ }
+
+ handle acquire_ensure(config::UserType user)
+ requires (!kHasPayload && config::kHasUser)
+ {
+ auto opt = acquire(user);
+ ASL_ASSERT_RELEASE(opt.has_value());
+ return opt.value();
+ }
+
+ option<handle> acquire(config::UserType user, const Payload& payload)
+ requires (kHasPayload && config::kHasUser)
+ {
+ return acquire_handle(payload).transform([user](internal_handle h)
+ {
+ return handle(h.index(), h.gen(), user);
+ });
+ }
+
+ handle acquire_ensure(config::UserType user, const Payload& payload)
+ requires (kHasPayload && config::kHasUser)
+ {
+ auto opt = acquire(user, payload);
+ ASL_ASSERT_RELEASE(opt.has_value());
+ return opt.value();
+ }
+
+ // @Todo Add a policy to abandon slots that reached max generation
+ void release(handle h)
+ {
+ if (Slot* slot = get_slot_if_valid(h); slot != nullptr)
+ {
+ const uint64_t next_gen = h.gen() == config::kMaxGen ? 0 : h.gen() + 1;
+
+ slot->is_active = false;
+
+ if (m_first_available.is_null())
+ {
+ slot->is_end_of_list = true;
+ slot->handle = internal_handle(h.index(), next_gen, 0);
+ }
+ else
+ {
+ slot->is_end_of_list = false;
+ slot->handle = internal_handle(m_first_available.index(), next_gen, 0);
+ }
+
+ m_first_available = internal_handle(h.index(), 0, 0);
+ }
+ }
+
+ bool is_valid(handle h) const
+ {
+ return get_slot_if_valid(h) != nullptr;
+ }
+
+ const Payload* get_payload(handle h) const
+ {
+ if (const Slot* slot = get_slot_if_valid(h); slot != nullptr)
+ {
+ return &slot->payload;
+ }
+ return nullptr;
+ }
+
+ option<Payload> exchange_payload(handle h, Payload new_payload)
+ {
+ if (Slot* slot = get_slot_if_valid(h); slot != nullptr)
+ {
+ return asl::exchange(slot->payload, new_payload);
+ }
+ return nullopt;
+ }
};
} // namespace asl