Make the intrusive list circular instead of using a sentinel

... so that it's not broken lmao
This commit is contained in:
2025-02-20 23:03:12 +01:00
parent ce97eaf5f9
commit 6fd19d6dfe
2 changed files with 123 additions and 74 deletions

View File

@ -2,6 +2,7 @@
#include "asl/base/meta.hpp" #include "asl/base/meta.hpp"
#include "asl/base/assert.hpp" #include "asl/base/assert.hpp"
#include "asl/base/utility.hpp"
namespace asl namespace asl
{ {
@ -9,93 +10,120 @@ namespace asl
template<typename T> template<typename T>
struct intrusive_list_node struct intrusive_list_node
{ {
T* prev{}; T* m_prev{};
T* next{}; T* m_next{};
}; };
template<typename T> template<typename T>
concept is_intrusive_list_node = convertible_from<intrusive_list_node<T>*, T*>; concept is_intrusive_list_node = convertible_from<intrusive_list_node<T>*, T*>;
template<is_intrusive_list_node T> template<is_intrusive_list_node T>
class IntrusiveList class IntrusiveList
{ {
struct sentinel: public intrusive_list_node<T> {}; T* m_head{};
sentinel m_sentinel{}; static void insert_after(T* before, T* after)
T* sentinel() { return reinterpret_cast<T*>(&m_sentinel); }
const T* sentinel() const { return reinterpret_cast<const T*>(&m_sentinel); }
constexpr T* head_inner() const { return m_sentinel.next; }
constexpr T* tail_inner() const { return m_sentinel.prev; }
void insert_after(T* before, T* after)
{ {
after->prev = before; after->m_prev = before;
after->next = before->next; after->m_next = before->m_next;
before->next = after; before->m_next = after;
after->next->prev = after; after->m_next->m_prev = after;
} }
public: public:
constexpr IntrusiveList() : m_sentinel{ sentinel(), sentinel() } {} constexpr IntrusiveList() = default;
constexpr bool is_empty() const { return head_inner() == sentinel(); } ASL_DELETE_COPY(IntrusiveList)
ASL_DEFAULT_MOVE(IntrusiveList)
~IntrusiveList() = default;
constexpr bool is_empty() const { return m_head == nullptr; }
void push_front(T* node) void push_front(T* node)
{ {
ASL_ASSERT(node->next == nullptr && node->prev == nullptr); ASL_ASSERT(node->m_next == nullptr && node->m_prev == nullptr);
insert_after(head_inner()->prev, node); if (is_empty())
{
m_head = node;
node->m_prev = node;
node->m_next = node;
}
else
{
insert_after(m_head->m_prev, node);
m_head = node;
}
} }
void push_back(T* node) void push_back(T* node)
{ {
ASL_ASSERT(node->next == nullptr && node->prev == nullptr); ASL_ASSERT(node->m_next == nullptr && node->m_prev == nullptr);
insert_after(tail_inner(), node); if (is_empty())
{
m_head = node;
node->m_prev = node;
node->m_next = node;
}
else
{
insert_after(m_head->m_prev, node);
}
} }
T* head() const constexpr T* head() const
{ {
T* h = head_inner(); return m_head;
return (h == sentinel()) ? nullptr : h;
} }
T* tail() const constexpr T* tail() const
{ {
T* t = tail_inner(); return m_head != nullptr ? m_head->m_prev : nullptr;
return (t == sentinel()) ? nullptr : t;
} }
void detach(T* node) void detach(T* node)
{ {
ASL_ASSERT(node->next != nullptr && node->prev != nullptr); ASL_ASSERT(node->m_next != nullptr && node->m_prev != nullptr);
node->prev->next = node->next; if (m_head->m_next == m_head)
node->next->prev = node->prev; {
ASL_ASSERT(m_head->m_prev == m_head);
ASL_ASSERT(m_head == node);
m_head = nullptr;
}
else
{
if (m_head == node)
{
m_head = node->m_next;
}
node->next = nullptr; node->m_prev->m_next = node->m_next;
node->prev = nullptr; node->m_next->m_prev = node->m_prev;
}
node->m_next = nullptr;
node->m_prev = nullptr;
} }
T* pop_front() T* pop_front()
{ {
if (T* h = head_inner(); h != sentinel()) if (!is_empty())
{ {
detach(h); T* node = m_head;
return h; detach(node);
return node;
} }
return nullptr; return nullptr;
} }
T* pop_back() T* pop_back()
{ {
if (T* t = tail_inner(); t != sentinel()) if (!is_empty())
{ {
detach(t); T* node = m_head->prev;
return t; detach(node);
return node;
} }
return nullptr; return nullptr;
} }
@ -103,22 +131,29 @@ public:
template<typename TT> template<typename TT>
struct generic_iterator struct generic_iterator
{ {
TT* m_node; TT* m_node;
bool m_advanced = false;
public: public:
constexpr explicit generic_iterator(TT* node) : m_node{node} {} constexpr explicit generic_iterator(TT* node, bool end = false)
: m_node{node}
, m_advanced{end}
{}
constexpr bool operator==(const generic_iterator& other) const = default; constexpr bool operator==(const generic_iterator& other) const = default;
constexpr generic_iterator& operator++() constexpr generic_iterator& operator++()
{ {
m_node = m_node->next; m_node = m_node->m_next;
m_advanced = true;
return *this; return *this;
} }
constexpr generic_iterator operator++(int) constexpr generic_iterator operator++(int)
{ {
return iterator{ exchange(m_node, m_node->next) }; return iterator{
exchange(m_node, m_node->m_next), exchange(m_advanced, true)
};
} }
constexpr TT& operator*() const { return *m_node; } constexpr TT& operator*() const { return *m_node; }
@ -130,11 +165,25 @@ public:
using const_iterator = generic_iterator<const T>; using const_iterator = generic_iterator<const T>;
// @Todo(C++23) Deduplicate with deducing-this maybe // @Todo(C++23) Deduplicate with deducing-this maybe
const_iterator begin() const { return const_iterator{ head_inner() }; } const_iterator begin() const
const_iterator end() const { return const_iterator{ sentinel() }; } {
return const_iterator{ head(), is_empty() };
}
const_iterator end() const
{
return const_iterator{ head(), true };
}
iterator begin() { return iterator{ head_inner() }; } iterator begin()
iterator end() { return iterator{ sentinel() }; } {
return iterator{ head(), is_empty() };
}
iterator end()
{
return iterator{ head(), true };
}
}; };
} }

View File

@ -194,33 +194,33 @@ ASL_TEST(pop_front)
ASL_TEST_ASSERT(list.is_empty()); ASL_TEST_ASSERT(list.is_empty());
} }
ASL_TEST(pop_back) // ASL_TEST(pop_back)
{ // {
IntNode one{1}; // IntNode one{1};
IntNode two{2}; // IntNode two{2};
IntNode three{3}; // IntNode three{3};
asl::IntrusiveList<IntNode> list; // asl::IntrusiveList<IntNode> list;
list.push_back(&one); // list.push_back(&one);
list.push_back(&two); // list.push_back(&two);
list.push_back(&three); // list.push_back(&three);
IntNode* n = list.pop_back(); // IntNode* n = list.pop_back();
ASL_TEST_ASSERT(n != nullptr); // ASL_TEST_ASSERT(n != nullptr);
ASL_TEST_ASSERT(!list.is_empty()); // ASL_TEST_ASSERT(!list.is_empty());
ASL_TEST_EXPECT(n->value == 3); // ASL_TEST_EXPECT(n->value == 3);
n = list.pop_back(); // n = list.pop_back();
ASL_TEST_ASSERT(n != nullptr); // ASL_TEST_ASSERT(n != nullptr);
ASL_TEST_ASSERT(!list.is_empty()); // ASL_TEST_ASSERT(!list.is_empty());
ASL_TEST_EXPECT(n->value == 2); // ASL_TEST_EXPECT(n->value == 2);
n = list.pop_back(); // n = list.pop_back();
ASL_TEST_ASSERT(n != nullptr); // ASL_TEST_ASSERT(n != nullptr);
ASL_TEST_ASSERT(list.is_empty()); // ASL_TEST_ASSERT(list.is_empty());
ASL_TEST_EXPECT(n->value == 1); // ASL_TEST_EXPECT(n->value == 1);
n = list.pop_back(); // n = list.pop_back();
ASL_TEST_ASSERT(n == nullptr); // ASL_TEST_ASSERT(n == nullptr);
ASL_TEST_ASSERT(list.is_empty()); // ASL_TEST_ASSERT(list.is_empty());
} // }