Skip to content

Commit eab471d

Browse files
committed
Simplify API by building a single doublylinkedlist
1 parent f1962e5 commit eab471d

File tree

7 files changed

+545
-481
lines changed

7 files changed

+545
-481
lines changed

include/nbl/core/alloc/PoolAddressAllocator.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ class PoolAddressAllocator : public AddressAllocatorBase<PoolAddressAllocator<_s
2929

3030
#ifdef _NBL_DEBUG
3131
assert(Base::checkResize(newBuffSz,Base::alignOffset));
32-
assert(freeStackCtr==0u);
3332
#endif // _NBL_DEBUG
3433

3534
for (_size_type i=0u; i<freeStackCtr; i++)
Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
// Copyright (C) 2018-2020 - DevSH Graphics Programming Sp. z O.O.
2+
// This file is part of the "Nabla Engine".
3+
// For conditions of distribution and use, see copyright notice in nabla.h
4+
5+
#ifndef __NBL_CORE_CONTAINERS_DOUBLY_LINKED_LIST_H_INCLUDED__
6+
#define __NBL_CORE_CONTAINERS_DOUBLY_LINKED_LIST_H_INCLUDED__
7+
8+
9+
#include "nbl/core/alloc/PoolAddressAllocator.h"
10+
#include "nbl/core/decl/Types.h"
11+
12+
#include <functional>
13+
14+
namespace nbl
15+
{
16+
namespace core
17+
{
18+
19+
//Struct for use in a doubly linked list. Stores data and pointers to next and previous elements the list, or invalid iterator if it is first/last
20+
template<typename Value>
21+
struct alignas(void*) SDoublyLinkedNode
22+
{
23+
using this_t = SDoublyLinkedNode<Value>;
24+
using value_t = Value;
25+
_NBL_STATIC_INLINE_CONSTEXPR uint32_t invalid_iterator = PoolAddressAllocator<uint32_t>::invalid_address;
26+
27+
Value data;
28+
uint32_t prev;
29+
uint32_t next;
30+
31+
SDoublyLinkedNode() {}
32+
SDoublyLinkedNode(const Value& val) : data(val)
33+
{
34+
prev = invalid_iterator;
35+
next = invalid_iterator;
36+
}
37+
SDoublyLinkedNode(Value&& val) : data(std::move(val))
38+
{
39+
prev = invalid_iterator;
40+
next = invalid_iterator;
41+
}
42+
SDoublyLinkedNode(SDoublyLinkedNode<Value>&& other) : data(std::move(other.data)), prev(std::move(other.prev)), next(std::move(other.next))
43+
{
44+
}
45+
46+
~SDoublyLinkedNode()
47+
{
48+
}
49+
50+
SDoublyLinkedNode<Value>& operator=(const SDoublyLinkedNode<Value>& other)
51+
{
52+
data = other.data;
53+
this->prev = other.prev;
54+
this->next = other.next;
55+
return *this;
56+
}
57+
58+
SDoublyLinkedNode<Value>& operator=(SDoublyLinkedNode<Value>&& other)
59+
{
60+
this->data = std::move(other.data);
61+
this->prev = std::move(other.prev);
62+
this->next = std::move(other.next);
63+
return *this;
64+
}
65+
};
66+
67+
template<typename Value>
68+
class DoublyLinkedList
69+
{
70+
public:
71+
using address_allocator_t = PoolAddressAllocator<uint32_t>;
72+
using node_t = SDoublyLinkedNode<Value>;
73+
using value_t = Value;
74+
using disposal_func_t = std::function<void(value_t&)>;
75+
76+
_NBL_STATIC_INLINE_CONSTEXPR uint32_t invalid_iterator = node_t::invalid_iterator;
77+
78+
// get the fixed capacity
79+
inline uint32_t getCapacity() const { return m_cap; }
80+
81+
//get node at iterator
82+
inline node_t* get(const uint32_t address)
83+
{
84+
return (m_array + address);
85+
}
86+
inline const node_t* get(const uint32_t address) const
87+
{
88+
return (m_array + address);
89+
}
90+
91+
//get node ptr of the first item in the list
92+
inline node_t* getBegin() { return m_array + m_begin; }
93+
inline const node_t* getBegin() const { return m_array + m_begin; }
94+
95+
//get node ptr of the last item in the list
96+
inline node_t* getBack() { return m_array + m_back; }
97+
inline const node_t* getBack() const { return m_array + m_back; }
98+
99+
//get index/iterator of the first element
100+
inline uint32_t getFirstAddress() const { return m_begin; }
101+
102+
//get index/iterator of the last element
103+
inline uint32_t getLastAddress() const { return m_back; }
104+
105+
//add new item to the list. This function does not make space to store the new node. in case the list is full, popBack() needs to be called beforehand
106+
inline void pushFront(value_t&& val)
107+
{
108+
insertAt(reserveAddress(), std::move(val));
109+
}
110+
111+
template <typename... Args>
112+
inline void emplaceFront(Args&&... args)
113+
{
114+
insertAt(reserveAddress(), value_t(std::forward<Args>(args)...));
115+
}
116+
117+
/**
118+
* @brief Resets list to initial state.
119+
*/
120+
inline void clear()
121+
{
122+
disposeAll();
123+
124+
m_addressAllocator = std::unique_ptr<address_allocator_t>(new address_allocator_t(m_reservedSpace, 0u, 0u, 1u, m_cap, 1u));
125+
m_back = invalid_iterator;
126+
m_begin = invalid_iterator;
127+
128+
}
129+
130+
//remove the last element in the list
131+
inline void popBack()
132+
{
133+
if (m_back == invalid_iterator)
134+
return;
135+
136+
auto backNode = getBack();
137+
if (backNode->prev != invalid_iterator)
138+
get(backNode->prev)->next = invalid_iterator;
139+
uint32_t temp = m_back;
140+
m_back = backNode->prev;
141+
common_delete(temp);
142+
}
143+
144+
//remove a node at nodeAddr from the list
145+
inline void erase(const uint32_t nodeAddr)
146+
{
147+
assert(nodeAddr != invalid_iterator);
148+
assert(nodeAddr < m_cap);
149+
node_t* node = get(nodeAddr);
150+
151+
if (m_back == nodeAddr)
152+
m_back = node->prev;
153+
if (m_begin == nodeAddr)
154+
m_begin = node->next;
155+
156+
common_detach(node);
157+
common_delete(nodeAddr);
158+
}
159+
160+
//move a node at nodeAddr to the front of the list
161+
inline void moveToFront(const uint32_t nodeAddr)
162+
{
163+
if (m_begin == nodeAddr || nodeAddr == invalid_iterator)
164+
return;
165+
166+
getBegin()->prev = nodeAddr;
167+
168+
auto node = get(nodeAddr);
169+
170+
if (m_back == nodeAddr)
171+
m_back = node->prev;
172+
173+
common_detach(node);
174+
node->next = m_begin;
175+
node->prev = invalid_iterator;
176+
m_begin = nodeAddr;
177+
}
178+
179+
/**
180+
* @brief Resizes the list by extending its capacity so it can hold more elements. Returns a bool indicating if capacity was indeed increased.
181+
*
182+
* @param [in] newCapacity New number of elements to hold. MUST be greater than current list capacity.
183+
*/
184+
inline bool grow(uint32_t newCapacity)
185+
{
186+
// Must at least make list grow
187+
if (newCapacity <= m_cap)
188+
return false;
189+
// Same as code found in ContiguousMemoryLinkedListBase to create aligned space
190+
const auto firstPart = core::alignUp(address_allocator_t::reserved_size(1u, newCapacity, 1u), alignof(node_t));
191+
void* newReservedSpace = _NBL_ALIGNED_MALLOC(firstPart + newCapacity * sizeof(node_t), alignof(node_t));
192+
193+
// Malloc failed, not possible to grow
194+
if (!newReservedSpace)
195+
return false;
196+
197+
node_t* newArray = reinterpret_cast<node_t*>(reinterpret_cast<uint8_t*>(newReservedSpace) + firstPart);
198+
199+
// Copy memory over to new buffer, then free old one
200+
memcpy(newArray, m_array, m_cap * sizeof(node_t));
201+
_NBL_ALIGNED_FREE(m_reservedSpace);
202+
203+
// Finally, create new address allocator from old one
204+
m_addressAllocator = std::unique_ptr<address_allocator_t>(new address_allocator_t(newCapacity, std::move(*(m_addressAllocator)), newReservedSpace));
205+
m_cap = newCapacity;
206+
m_array = newArray;
207+
m_reservedSpace = newReservedSpace;
208+
209+
return true;
210+
}
211+
212+
//Constructor, capacity determines the amount of allocated space
213+
DoublyLinkedList(const uint32_t capacity, disposal_func_t&& dispose_f = disposal_func_t()) : m_dispose_f(std::move(dispose_f))
214+
{
215+
const auto firstPart = core::alignUp(address_allocator_t::reserved_size(1u, capacity, 1u), alignof(node_t));
216+
m_reservedSpace = _NBL_ALIGNED_MALLOC(firstPart + capacity * sizeof(node_t), alignof(node_t));
217+
m_array = reinterpret_cast<node_t*>(reinterpret_cast<uint8_t*>(m_reservedSpace) + firstPart);
218+
219+
m_addressAllocator = std::unique_ptr<address_allocator_t>(new address_allocator_t(m_reservedSpace, 0u, 0u, 1u, capacity, 1u));
220+
m_cap = capacity;
221+
m_back = invalid_iterator;
222+
m_begin = invalid_iterator;
223+
}
224+
225+
DoublyLinkedList() = default;
226+
227+
DoublyLinkedList(const DoublyLinkedList& other) = delete;
228+
229+
DoublyLinkedList& operator=(const DoublyLinkedList& other) = delete;
230+
231+
DoublyLinkedList& operator=(DoublyLinkedList&& other)
232+
{
233+
m_addressAllocator = std::move(other.m_addressAllocator);
234+
m_reservedSpace = other.m_reservedSpace;
235+
m_array = other.m_array;
236+
m_dispose_f = std::move(other.m_dispose_f);
237+
m_cap = other.m_cap;
238+
m_back = other.m_back;
239+
m_begin = other.m_begin;
240+
241+
// Nullify other
242+
other.m_addressAllocator = nullptr;
243+
other.m_reservedSpace = nullptr;
244+
other.m_array = nullptr;
245+
other.m_cap = 0u;
246+
other.m_back = 0u;
247+
other.m_begin = 0u;
248+
return *this;
249+
}
250+
251+
~DoublyLinkedList()
252+
{
253+
disposeAll();
254+
_NBL_ALIGNED_FREE(m_reservedSpace);
255+
}
256+
257+
private:
258+
//allocate and get the address of the next free node
259+
inline uint32_t reserveAddress()
260+
{
261+
uint32_t addr = m_addressAllocator->alloc_addr(1u, 1u);
262+
return addr;
263+
}
264+
265+
//create a new node which stores data at already allocated address,
266+
inline void insertAt(uint32_t addr, value_t&& val)
267+
{
268+
assert(addr < m_cap);
269+
assert(addr != invalid_iterator);
270+
SDoublyLinkedNode<Value>* n = new(m_array + addr) SDoublyLinkedNode<Value>(std::move(val));
271+
n->prev = invalid_iterator;
272+
n->next = m_begin;
273+
274+
if (m_begin != invalid_iterator)
275+
getBegin()->prev = addr;
276+
if (m_back == invalid_iterator)
277+
m_back = addr;
278+
m_begin = addr;
279+
}
280+
281+
/**
282+
* @brief Calls disposal function on all elements of the list.
283+
*/
284+
inline void disposeAll()
285+
{
286+
if (m_dispose_f && m_begin != invalid_iterator)
287+
{
288+
auto* begin = getBegin();
289+
auto* back = getBack();
290+
while (begin != back)
291+
{
292+
m_dispose_f(begin->data);
293+
begin = get(begin->next);
294+
}
295+
m_dispose_f(back->data);
296+
}
297+
}
298+
299+
inline void common_delete(uint32_t address)
300+
{
301+
if (m_dispose_f)
302+
m_dispose_f(get(address)->data);
303+
get(address)->~node_t();
304+
m_addressAllocator->free_addr(address, 1u);
305+
}
306+
307+
inline void common_detach(node_t* node)
308+
{
309+
if (node->next != invalid_iterator)
310+
get(node->next)->prev = node->prev;
311+
if (node->prev != invalid_iterator)
312+
get(node->prev)->next = node->next;
313+
}
314+
315+
std::unique_ptr<address_allocator_t> m_addressAllocator;
316+
void* m_reservedSpace;
317+
node_t* m_array;
318+
319+
uint32_t m_cap;
320+
uint32_t m_back;
321+
uint32_t m_begin;
322+
disposal_func_t m_dispose_f;
323+
};
324+
325+
326+
}
327+
}
328+
329+
330+
#endif

0 commit comments

Comments
 (0)