diff options
-rw-r--r-- | .bazelrc | 3 | ||||
-rw-r--r-- | MODULE.bazel | 7 | ||||
-rw-r--r-- | MODULE.bazel.lock | 4 | ||||
-rw-r--r-- | asl/handle_pool/BUILD.bazel | 2 | ||||
-rw-r--r-- | asl/handle_pool/index_pool.hpp | 308 | ||||
-rw-r--r-- | asl/handle_pool/index_pool_tests.cpp | 256 | ||||
-rw-r--r-- | asl/testing/testing.cpp | 8 |
7 files changed, 532 insertions, 56 deletions
@@ -1,3 +1,6 @@ +common --registry=https://bcr.bazel.build/ +common --registry=https://bazel.stevenlr.com/registry/ + startup --windows_enable_symlinks build:windows --enable_runfiles=true diff --git a/MODULE.bazel b/MODULE.bazel index 6c224af..8d856b3 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -11,12 +11,7 @@ bazel_dep(name = "rules_cc", version = "0.1.1") cc_configure = use_extension("@rules_cc//cc:extensions.bzl", "cc_configure_extension") use_repo(cc_configure, "local_config_cc") -bazel_dep(name = "hedron_compile_commands", dev_dependency = True) -git_override( - module_name = "hedron_compile_commands", - remote = "https://github.com/hedronvision/bazel-compile-commands-extractor.git", - commit = "4f28899228fb3ad0126897876f147ca15026151e", -) +bazel_dep(name = "hedron_compile_commands", version = "0.1.0", dev_dependency = True) bazel_dep(name = "rules_python", version = "1.3.0") python = use_extension("@rules_python//python/extensions:python.bzl", "python") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index a47fe3f..55c4990 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1,6 +1,9 @@ { "lockFileVersion": 18, "registryFileHashes": { + "https://bazel.stevenlr.com/registry/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bazel.stevenlr.com/registry/modules/hedron_compile_commands/0.1.0/MODULE.bazel": "5623ba8f732a01246c388bccebf924357e452314a178f179d3b375b623d5a359", + "https://bazel.stevenlr.com/registry/modules/hedron_compile_commands/0.1.0/source.json": "c55f6caa3eb9fb027af66949c23ca537214eb32b0316ae95bcc496f3cd8406b9", "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", @@ -39,6 +42,7 @@ "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4", "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", + "https://bcr.bazel.build/modules/hedron_compile_commands/0.1.0/MODULE.bazel": "not found", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", diff --git a/asl/handle_pool/BUILD.bazel b/asl/handle_pool/BUILD.bazel index e5048d3..bdaa711 100644 --- a/asl/handle_pool/BUILD.bazel +++ b/asl/handle_pool/BUILD.bazel @@ -16,6 +16,7 @@ cc_library( "//asl/memory:allocator", "//asl/base", "//asl/containers:chunked_buffer", + "//asl/types:option", ], visibility = ["//visibility:public"], ) @@ -27,6 +28,7 @@ cc_test( ], deps = [ ":index_pool", + "//asl/hashing", "//asl/tests:utils", "//asl/testing", ], diff --git a/asl/handle_pool/index_pool.hpp b/asl/handle_pool/index_pool.hpp index 377f6f4..56345e8 100644 --- a/asl/handle_pool/index_pool.hpp +++ b/asl/handle_pool/index_pool.hpp @@ -6,82 +6,296 @@ #include "asl/base/meta.hpp" #include "asl/containers/chunked_buffer.hpp" #include "asl/memory/allocator.hpp" +#include "asl/types/option.hpp" namespace asl { template< - int kIndexSize_, - int kGenSize_, - int kUserSize_ = 0 + int kIndexBits_, + int kGenBits_, + typename UserType_ = empty, + int kUserBits_ = 0 > -requires ( - kUserSize_ >= 0 - && kGenSize_ > 0 - && kIndexSize_ > 0 - && (kUserSize_ + kGenSize_ + kIndexSize_ <= 63) -) +requires (kIndexBits_ > 0 && kGenBits_ > 0 && kUserBits_ >= 0) struct index_pool_config { - static constexpr int kUserSize = kUserSize_; - static constexpr int kGenSize = kGenSize_; - static constexpr int kIndexSize = kIndexSize_; + static constexpr bool kHasUser = !same_as<UserType_, empty>; - static constexpr bool kHasUser = kUserSize > 0; + using UserType = UserType_; + using PrimitiveUserType = smallest_unsigned_integer_type_for_width<size_of<UserType> * 8>; - using handle_type = smallest_unsigned_integer_type_for_width<kGenSize + kIndexSize + kUserSize>; - using user_type = select_t<kHasUser, smallest_unsigned_integer_type_for_width<kUserSize>, empty>; -}; + 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 { + if constexpr (!kHasUser) { return 0; }; + return kUserBits_ == 0 ? size_of<UserType> * 8 : kUserBits_; + }(); + + static_assert(kUserBits <= size_of<UserType> * 8); + + static constexpr int kIndexBits = kIndexBits_; + static constexpr int kGenBits = kGenBits_; + + static_assert(kIndexBits + kGenBits + kUserBits <= 63); + + 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 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< - int kIndexSize_, - int kGenSize_, - typename UserType = empty, - int kUserSize_ = 0 + int kIndexBits_, + int kGenBits_, + typename UserType_ = empty, + int kUserBits_ = 0 > -requires ( - same_as<UserType, empty> || (kUserSize_ <= size_of<UserType> * 8 && trivially_copy_constructible<UserType>) -) -class index_pool_handle_base +class index_pool_handle { - static constexpr int kUserSizeComputed = - same_as<UserType, empty> ? 0 : (kUserSize_ == 0 ? size_of<UserType> * 8 : kUserSize_); // NOLINT - public: - using config = index_pool_config<kIndexSize_, kGenSize_, kUserSizeComputed>; + using config = index_pool_config<kIndexBits_, kGenBits_, UserType_, kUserBits_>; private: - using handle_type = config::handle_type; - using user_type = config::user_type; - using user_type_external = UserType; - - static constexpr handle_type kHasValueMask = ~(~handle_type{0} >> 1); - - static constexpr handle_type kIndexMask = (handle_type{1} << config::kIndexSize) - 1; + config::HandleType m_handle{}; - static constexpr int kGenShift = config::kIndexSize; - static constexpr handle_type kGenMask = ((handle_type{1} << config::kGenSize) - 1) << kGenShift; +public: + constexpr index_pool_handle() = default; - static constexpr int kUserShift = config::kIndexSize + config::kGenSize; - static constexpr handle_type kUserMask = ((handle_type{1} << config::kUserSize) - 1) << kUserShift; - - handle_type m_handle{}; + constexpr explicit index_pool_handle(niche_t) + : m_handle{config::kNiche} + {} -public: - [[nodiscard]] constexpr bool has_value() const { return m_handle & kHasValueMask; } + constexpr index_pool_handle(uint64_t index, uint64_t gen) + requires (!config::kHasUser) + : m_handle{static_cast<config::HandleType>( + config::kValidMask | + (index & config::kIndexMask) | + ((gen << config::kGenShift) & config::kGenMask))} + { + ASL_ASSERT((index & uint64_t{config::kIndexMask}) == index); + ASL_ASSERT((gen & (uint64_t{config::kGenMask} >> config::kGenShift)) == gen); + } - [[nodiscard]] constexpr user_type_external user() const + constexpr index_pool_handle(uint64_t index, uint64_t gen, config::UserType user) requires config::kHasUser + : m_handle{static_cast<config::HandleType>( + config::kValidMask | + (index & config::kIndexMask) | + ((gen << config::kGenShift) & config::kGenMask) | + ((static_cast<config::HandleType>(bit_cast<typename config::PrimitiveUserType>(user)) << config::kUserShift) & config::kUserMask))} + { + ASL_ASSERT((index & uint64_t{config::kIndexMask}) == index); + ASL_ASSERT((gen & (uint64_t{config::kGenMask} >> config::kGenShift)) == gen); + ASL_ASSERT((bit_cast<typename config::PrimitiveUserType>(user) & (uint64_t{config::kUserMask} >> config::kUserShift)) == bit_cast<typename config::PrimitiveUserType>(user)); + } + + constexpr bool is_null(this index_pool_handle self) { - return bit_cast<user_type_external>( - static_cast<user_type>((m_handle & kUserMask) >> kUserShift) - ); + return !(self.m_handle & config::kValidMask); + } + + constexpr uint64_t index(this index_pool_handle self) + { + return self.m_handle & config::kIndexMask; + } + + constexpr uint64_t gen(this index_pool_handle self) + { + return (self.m_handle & config::kGenMask) >> config::kGenShift; + } + + constexpr config::UserType user(this index_pool_handle self) + { + return bit_cast<typename config::UserType>(static_cast<config::PrimitiveUserType>( + ((self.m_handle & config::kUserMask) >> config::kUserShift))); + } + + 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; + + static constexpr bool kHasPayload = !same_as<Payload, empty>; + + // @Todo Remove need for default constructible & trivially destructible for payload + // Use maybe_uninit for it + + // @Todo Use dummy user type with inner handle type -> no need to set user data when allocating handle + + 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; + + 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. + handle m_first_available; + + static constexpr handle make_handle(uint64_t index, uint64_t gen, [[maybe_unused]] config::UserType user_data) + { + if constexpr (config::kHasUser) + { + return handle(index, gen, user_data); + } + else + { + return handle(index, gen); + } + }; + + void allocate_new_slot(config::UserType user) + { + const auto new_index = static_cast<uint64_t>(m_slots.size()); + if (new_index > config::kMaxIndex) { return; } + + const handle new_handle = make_handle(new_index, 0, user); + + m_slots.push(Slot{ + .is_end_of_list = true, + .is_active = false, + .handle = new_handle, + .payload = Payload{} + }); + + m_first_available = new_handle; + } + + option<handle> acquire_handle(const Payload& payload, config::UserType user) + { + if (m_first_available.is_null()) + { + allocate_new_slot(user); + } + + 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 ? handle{} : slot.handle; + + slot.is_active = true; + slot.payload = payload; + + return make_handle(index, slot.handle.gen(), user); + } + + auto get_slot_if_valid(this auto&& self, handle h) + -> copy_const_t<decltype(self), Slot>* + { + 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(const Payload& payload) + { + return acquire_handle(payload, {}); + } + + handle acquire_ensure(const Payload& payload) + { + auto opt = acquire(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 = make_handle(h.index(), next_gen, {}); + } + else + { + slot->is_end_of_list = false; + slot->handle = make_handle(m_first_available.index(), next_gen, {}); + } + + m_first_available = h; + } + } + + bool is_valid(handle h) const + { + return get_slot_if_valid(h) != nullptr; + } }; } // namespace asl diff --git a/asl/handle_pool/index_pool_tests.cpp b/asl/handle_pool/index_pool_tests.cpp index 1dd2816..329fb4c 100644 --- a/asl/handle_pool/index_pool_tests.cpp +++ b/asl/handle_pool/index_pool_tests.cpp @@ -3,8 +3,262 @@ // SPDX-License-Identifier: BSD-3-Clause #include "asl/testing/testing.hpp" +#include "asl/handle_pool/index_pool.hpp" +#include "asl/hashing/hash.hpp" -ASL_TEST(test) +enum Flags: uint8_t { + kFlag0 = 0, + kFlag1 = 1, + kFlag2 = 2, +}; + +using Cfg1 = asl::index_pool_config<4, 3>; +static_assert(!Cfg1::kHasUser); +static_assert(Cfg1::kUserBits == 0); +static_assert(asl::same_as<Cfg1::HandleType, uint8_t>); +static_assert(Cfg1::kValidMask == uint8_t{0x80}); +static_assert(Cfg1::kIndexMask == uint8_t{0x0f}); +static_assert(Cfg1::kGenMask == uint8_t{0x70}); +static_assert(Cfg1::kGenShift == 4); +static_assert(Cfg1::kMaxGen == 7); +static_assert(Cfg1::kMaxIndex == 15); + +using Cfg2 = asl::index_pool_config<5, 5, Flags>; +static_assert(Cfg2::kHasUser); +static_assert(Cfg2::kUserBits == 8); +static_assert(asl::same_as<Cfg2::PrimitiveUserType, uint8_t>); +static_assert(asl::same_as<Cfg2::HandleType, uint32_t>); +static_assert(Cfg2::kValidMask == uint32_t{0x8000'0000}); +static_assert(Cfg2::kIndexMask == uint32_t{0x0000'001f}); +static_assert(Cfg2::kGenMask == uint32_t{0x0000'03e0}); +static_assert(Cfg2::kUserMask == uint32_t{0x0003'fc00}); +static_assert(Cfg2::kGenShift == 5); +static_assert(Cfg2::kUserShift == 10); +static_assert(Cfg2::kMaxGen == 31); +static_assert(Cfg2::kMaxIndex == 31); + +using Cfg3 = asl::index_pool_config<5, 6, Flags, 4>; +static_assert(Cfg3::kHasUser); +static_assert(Cfg3::kUserBits == 4); +static_assert(asl::same_as<Cfg3::PrimitiveUserType, uint8_t>); +static_assert(asl::same_as<Cfg3::HandleType, uint16_t>); +static_assert(Cfg3::kValidMask == uint16_t{0x8000}); +static_assert(Cfg3::kIndexMask == uint16_t{0x001f}); +static_assert(Cfg3::kGenMask == uint16_t{0x07e0}); +static_assert(Cfg3::kUserMask == uint16_t{0x7800}); +static_assert(Cfg3::kGenShift == 5); +static_assert(Cfg3::kUserShift == 11); +static_assert(Cfg3::kMaxGen == 63); +static_assert(Cfg3::kMaxIndex == 31); + +static_assert(asl::default_constructible<asl::index_pool_handle<5, 5, uint8_t>>); +static_assert(asl::trivially_copy_constructible<asl::index_pool_handle<5, 5, uint8_t>>); +static_assert(asl::trivially_move_constructible<asl::index_pool_handle<5, 5, uint8_t>>); +static_assert(asl::trivially_copy_assignable<asl::index_pool_handle<5, 5, uint8_t>>); +static_assert(asl::trivially_move_assignable<asl::index_pool_handle<5, 5, uint8_t>>); +static_assert(asl::trivially_destructible<asl::index_pool_handle<5, 5, uint8_t>>); + +static_assert(asl::hashable<asl::index_pool_handle<5, 5, uint8_t>>); +static_assert(asl::has_niche<asl::index_pool_handle<5, 5, uint8_t>>); + +ASL_TEST(default_is_invalid) +{ + const asl::index_pool_handle<5, 5, uint8_t> idx; + ASL_TEST_EXPECT(idx.is_null()); +} + +ASL_TEST(niche_is_invalid) +{ + const asl::index_pool_handle<5, 5, uint8_t> idx{asl::niche_t{}}; + ASL_TEST_EXPECT(idx.is_null()); +} + +ASL_TEST(construct) +{ + const asl::index_pool_handle<5, 5> idx(9, 11); + ASL_TEST_EXPECT(!idx.is_null()); + ASL_TEST_EXPECT(idx.index() == 9); + ASL_TEST_EXPECT(idx.gen() == 11); +} + +ASL_TEST(construct_user) +{ + const asl::index_pool_handle<5, 5, Flags, 4> idx(9, 11, kFlag2); + ASL_TEST_EXPECT(!idx.is_null()); + ASL_TEST_EXPECT(idx.index() == 9); + ASL_TEST_EXPECT(idx.gen() == 11); + ASL_TEST_EXPECT(idx.user() == kFlag2); + static_assert(asl::same_as<Flags, decltype(idx.user())>); +} + +ASL_TEST(compare) // NOLINT +{ + const asl::index_pool_handle<5, 5, Flags, 4> idx_default; + const asl::index_pool_handle<5, 5, Flags, 4> idx0; + const asl::index_pool_handle<5, 5, Flags, 4> idx1(9, 11, kFlag2); + const asl::index_pool_handle<5, 5, Flags, 4> idx2(9, 11, kFlag1); + const asl::index_pool_handle<5, 5, Flags, 4> idx3(9, 11, kFlag1); + const asl::index_pool_handle<5, 5, Flags, 4> idx4(9, 10, kFlag2); + const asl::index_pool_handle<5, 5, Flags, 4> idx5(8, 11, kFlag2); + + ASL_TEST_EXPECT(idx0 == idx_default); + + ASL_TEST_EXPECT(idx0 != idx1); + ASL_TEST_EXPECT(idx0 != idx2); + ASL_TEST_EXPECT(idx0 != idx3); + ASL_TEST_EXPECT(idx0 != idx4); + ASL_TEST_EXPECT(idx0 != idx5); + + ASL_TEST_EXPECT(idx1 != idx2); + ASL_TEST_EXPECT(idx1 != idx3); + ASL_TEST_EXPECT(idx1 != idx4); + ASL_TEST_EXPECT(idx1 != idx5); + + ASL_TEST_EXPECT(idx2 == idx3); + ASL_TEST_EXPECT(idx2 != idx4); + ASL_TEST_EXPECT(idx2 != idx5); + + ASL_TEST_EXPECT(idx3 != idx4); + ASL_TEST_EXPECT(idx3 != idx5); + + ASL_TEST_EXPECT(idx4 != idx5); +} + +ASL_TEST(hashing) // NOLINT +{ + const asl::index_pool_handle<4, 4> idx0(asl::niche_t{}); + const asl::index_pool_handle<4, 4> idx1{}; + const asl::index_pool_handle<4, 4> idx2(1, 1); + const asl::index_pool_handle<4, 4> idx3(1, 1); + const asl::index_pool_handle<4, 4> idx4(2, 1); + const asl::index_pool_handle<4, 4> idx5(1, 2); + + ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx1)); + ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx2)); + ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx3)); + ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx4)); + ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx5)); + + ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx2)); + ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx3)); + ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx4)); + ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx5)); + + ASL_TEST_EXPECT(asl::hash_value(idx2) == asl::hash_value(idx3)); + ASL_TEST_EXPECT(asl::hash_value(idx2) != asl::hash_value(idx4)); + ASL_TEST_EXPECT(asl::hash_value(idx2) != asl::hash_value(idx5)); + + ASL_TEST_EXPECT(asl::hash_value(idx3) != asl::hash_value(idx4)); + ASL_TEST_EXPECT(asl::hash_value(idx3) != asl::hash_value(idx5)); + + ASL_TEST_EXPECT(asl::hash_value(idx4) != asl::hash_value(idx5)); +} + +ASL_TEST(hashing_in_option) // NOLINT { + const asl::option<asl::index_pool_handle<4, 4>> idx0; + const asl::option<asl::index_pool_handle<4, 4>> idx1{asl::index_pool_handle<4, 4>()}; + const asl::option<asl::index_pool_handle<4, 4>> idx2{asl::index_pool_handle<4, 4>(1, 1)}; + const asl::option<asl::index_pool_handle<4, 4>> idx3{asl::index_pool_handle<4, 4>(1, 1)}; + const asl::option<asl::index_pool_handle<4, 4>> idx4{asl::index_pool_handle<4, 4>(2, 1)}; + const asl::option<asl::index_pool_handle<4, 4>> idx5{asl::index_pool_handle<4, 4>(1, 2)}; + + ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx1)); + ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx2)); + ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx3)); + ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx4)); + ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx5)); + + ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx2)); + ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx3)); + ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx4)); + ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx5)); + + ASL_TEST_EXPECT(asl::hash_value(idx2) == asl::hash_value(idx3)); + ASL_TEST_EXPECT(asl::hash_value(idx2) != asl::hash_value(idx4)); + ASL_TEST_EXPECT(asl::hash_value(idx2) != asl::hash_value(idx5)); + + ASL_TEST_EXPECT(asl::hash_value(idx3) != asl::hash_value(idx4)); + ASL_TEST_EXPECT(asl::hash_value(idx3) != asl::hash_value(idx5)); + + ASL_TEST_EXPECT(asl::hash_value(idx4) != asl::hash_value(idx5)); +} + +ASL_TEST(simple_pool) // NOLINT +{ + using Pool = asl::IndexPool<8, 8>; + Pool pool; + + auto a = pool.acquire_ensure({}); + auto b = pool.acquire_ensure({}); + + ASL_TEST_EXPECT(!a.is_null()); + ASL_TEST_EXPECT(!b.is_null()); + ASL_TEST_EXPECT(a.index() == 0); + ASL_TEST_EXPECT(b.index() == 1); + ASL_TEST_EXPECT(a.gen() == 0); + ASL_TEST_EXPECT(b.gen() == 0); + + ASL_TEST_EXPECT(a != b); + + ASL_TEST_EXPECT(pool.is_valid(a)); + ASL_TEST_EXPECT(pool.is_valid(b)); + + pool.release(a); + + ASL_TEST_EXPECT(!pool.is_valid(a)); + ASL_TEST_EXPECT(pool.is_valid(b)); + + auto c = pool.acquire_ensure({}); + + ASL_TEST_EXPECT(!c.is_null()); + ASL_TEST_EXPECT(c.index() == 0); + ASL_TEST_EXPECT(c.gen() == 1); + + ASL_TEST_EXPECT(a != c); + ASL_TEST_EXPECT(b != c); + + ASL_TEST_EXPECT(!pool.is_valid(a)); + ASL_TEST_EXPECT(pool.is_valid(b)); + ASL_TEST_EXPECT(pool.is_valid(c)); + + pool.release(b); + + ASL_TEST_EXPECT(!pool.is_valid(a)); + ASL_TEST_EXPECT(!pool.is_valid(b)); + ASL_TEST_EXPECT(pool.is_valid(c)); + + pool.release(c); + + ASL_TEST_EXPECT(!pool.is_valid(a)); + ASL_TEST_EXPECT(!pool.is_valid(b)); + ASL_TEST_EXPECT(!pool.is_valid(c)); +} + +ASL_TEST(pool_acquire_release_a_lot) +{ + using Pool = asl::IndexPool<3, 3>; + Pool pool; + + for (int i = 0; i < 80; ++i) + { + pool.release(pool.acquire_ensure({})); + } +} + +ASL_TEST(pool_acquire_past_capacity) +{ + using Pool = asl::IndexPool<3, 3>; + Pool pool; + + for (int i = 0; i < 8; ++i) + { + ASL_TEST_EXPECT(pool.acquire({}).has_value()); + } + + for (int i = 0; i < 8; ++i) + { + ASL_TEST_EXPECT(!pool.acquire({}).has_value()); + } } diff --git a/asl/testing/testing.cpp b/asl/testing/testing.cpp index 53e2bb0..45035d3 100644 --- a/asl/testing/testing.cpp +++ b/asl/testing/testing.cpp @@ -69,7 +69,7 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) asl::testing::Test* failed_head = nullptr; - for (auto* it = g_state.head; it != nullptr; it = it->m_next) + for (auto* it = g_state.head; it != nullptr;) { asl::eprint(GREEN("[ RUN ]") " {}\n", it->m_case_name); @@ -80,13 +80,17 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) { asl::eprint(GREEN("[ OK ]") " {}\n", it->m_case_name); pass += 1; + it = it->m_next; } else { asl::eprint(RED("[ FAILED ]") " {}\n", it->m_case_name); fail += 1; - it->m_next = asl::exchange(failed_head, it); + auto* this_test = it; + it = it->m_next; + + this_test->m_next = asl::exchange(failed_head, this_test); } } |