diff --git a/AK/DoublyLinkedList.h b/AK/DoublyLinkedList.h index bca175d95de..403110c7526 100644 --- a/AK/DoublyLinkedList.h +++ b/AK/DoublyLinkedList.h @@ -23,8 +23,8 @@ public: m_node = m_node->next; return *this; } - ElementType& operator*() { return m_node->value; } - ElementType* operator->() { return &m_node->value; } + ElementType& operator*() { return m_node->value(); } + ElementType* operator->() { return &m_node->value(); } [[nodiscard]] bool is_end() const { return !m_node; } static DoublyLinkedListIterator universal_end() { return DoublyLinkedListIterator(nullptr); } @@ -37,25 +37,39 @@ private: typename ListType::Node* m_node; }; -template +template class DoublyLinkedList { private: struct Node { template explicit Node(U&& v) - : value(forward(v)) { + new (m_value) T(forward(v)); static_assert( requires { T(v); }, "Conversion operator is missing."); } - T value; + + T const& value() const { return *bit_cast(&m_value); } + T& value() { return *bit_cast(&m_value); } + Node* next { nullptr }; Node* prev { nullptr }; + + private: + alignas(T) u8 m_value[sizeof(T)]; }; public: DoublyLinkedList() = default; - ~DoublyLinkedList() { clear(); } + ~DoublyLinkedList() + { + clear(); + if constexpr (node_cache_size > 0) { + for (size_t i = 0; i < m_node_cache.used_count; ++i) + delete m_node_cache.nodes[i]; + m_node_cache.used_count = 0; + } + } [[nodiscard]] bool is_empty() const { return !m_head; } @@ -63,32 +77,42 @@ public: { for (auto* node = m_head; node;) { auto* next = node->next; - delete node; + drop_node(node); node = next; } m_head = nullptr; m_tail = nullptr; + m_size = 0; } [[nodiscard]] T& first() { VERIFY(m_head); - return m_head->value; + return m_head->value(); } [[nodiscard]] T const& first() const { VERIFY(m_head); - return m_head->value; + return m_head->value(); } [[nodiscard]] T& last() { VERIFY(m_head); - return m_tail->value; + return m_tail->value(); } [[nodiscard]] T const& last() const { VERIFY(m_head); - return m_tail->value; + return m_tail->value(); + } + + [[nodiscard]] T& unchecked_last() + { + return m_tail->value(); + } + [[nodiscard]] T const& unchecked_last() const + { + return m_tail->value(); } template @@ -96,9 +120,10 @@ public: { static_assert( requires { T(value); }, "Conversion operator is missing."); - auto* node = new (nothrow) Node(forward(value)); + auto* node = make_node(forward(value)); if (!node) return Error::from_errno(ENOMEM); + m_size += 1; if (!m_head) { VERIFY(!m_tail); m_head = node; @@ -117,9 +142,10 @@ public: ErrorOr try_prepend(U&& value) { static_assert(IsSame); - auto* node = new (nothrow) Node(forward(value)); + auto* node = make_node(forward(value)); if (!node) return Error::from_errno(ENOMEM); + m_size += 1; if (!m_head) { VERIFY(!m_tail); m_head = node; @@ -189,12 +215,95 @@ public: VERIFY(node == m_tail); m_tail = node->prev; } - delete node; + m_size -= 1; + drop_node(node); + } + + T take_first() + { + VERIFY(m_head); + auto value = move(m_head->value()); + auto* old_head = m_head; + m_head = m_head->next; + if (m_head) + m_head->prev = nullptr; + else + m_tail = nullptr; // We removed the only element, no more elements left. + drop_node(old_head); + m_size -= 1; + return value; + } + + T take_last() + { + VERIFY(m_tail); + auto value = move(m_tail->value()); + auto* old_tail = m_tail; + m_tail = m_tail->prev; + if (m_tail) + m_tail->next = nullptr; + else + m_head = nullptr; // We removed the only element, no more elements left. + drop_node(old_tail); + m_size -= 1; + return value; + } + + size_t size() const { return m_size; } + + template + void ensure_capacity(size_t new_capacity, F make_default_value = [] -> T { return T {}; }) + { + if constexpr (node_cache_size == 0) + return; + + if (m_size >= new_capacity) + return; + auto const rest = min(new_capacity - m_size, node_cache_size); + for (size_t i = m_node_cache.used_count; i <= rest; ++i) + m_node_cache.nodes[m_node_cache.used_count++] = make_node(make_default_value()); } private: + void drop_node(Node* node) + { + if constexpr (node_cache_size > 0) { + if (m_node_cache.used_count + 1 < node_cache_size) { + node->value().~T(); + m_node_cache.nodes[m_node_cache.used_count++] = node; + return; + } + } + + node->value().~T(); + delete node; + } + + template + Node* make_node(Args&&... args) + { + if constexpr (node_cache_size > 0) { + if (m_node_cache.used_count > 0) { + auto* node = m_node_cache.nodes[--m_node_cache.used_count]; + new (node) Node(forward(args)...); + return node; + } + } + + return new (nothrow) Node(forward(args)...); + } + Node* m_head { nullptr }; Node* m_tail { nullptr }; + size_t m_size { 0 }; + + struct NonemptyNodeCache { + Array nodes; + size_t used_count { 0 }; + }; + using NodeCache = Conditional<(node_cache_size > 0), NonemptyNodeCache, Empty>; + + NO_UNIQUE_ADDRESS NodeCache m_node_cache; }; } diff --git a/AK/Forward.h b/AK/Forward.h index f588e70bd60..d5212008820 100644 --- a/AK/Forward.h +++ b/AK/Forward.h @@ -84,7 +84,7 @@ class Atomic; template class SinglyLinkedList; -template +template class DoublyLinkedList; template diff --git a/Tests/AK/TestDoublyLinkedList.cpp b/Tests/AK/TestDoublyLinkedList.cpp index 480b0c585d3..ab22dc9c3fd 100644 --- a/Tests/AK/TestDoublyLinkedList.cpp +++ b/Tests/AK/TestDoublyLinkedList.cpp @@ -41,3 +41,50 @@ TEST_CASE(should_find_const) EXPECT_EQ(sut.end(), sut.find(42)); } + +TEST_CASE(take_first) +{ + auto sut = make_list(); + + EXPECT_EQ(0, sut.take_first()); + EXPECT_EQ(1, sut.first()); + EXPECT_EQ(9, sut.last()); + EXPECT_EQ(9u, sut.size()); +} + +TEST_CASE(take_last) +{ + auto sut = make_list(); + + EXPECT_EQ(9, sut.take_last()); + EXPECT_EQ(8, sut.last()); + EXPECT_EQ(0, sut.first()); + EXPECT_EQ(9u, sut.size()); +} + +TEST_CASE(take_last_all) +{ + auto sut = make_list(); + + for (int i = 0; i < 10; ++i) + EXPECT_EQ(9 - i, sut.take_last()); + + EXPECT_EQ(sut.size(), 0u); +} + +TEST_CASE(basic_node_cache) +{ + // FIXME: Add more comprehensive tests. + DoublyLinkedList list; + list.append(0); + list.append(1); + + Vector seen_ptrs; + for (auto& entry : list) + seen_ptrs.append(&entry); + + list.take_last(); + + list.append(2); + EXPECT(seen_ptrs.contains_slow(&list.last())); // node cache should have reused the last node +}