1//! @file memory.hpp
2//! @author ryftchen
3//! @brief The declarations (memory) in the utility module.
4//! @version 0.1.0
5//! @copyright Copyright (c) 2022-2025 ryftchen. All rights reserved.
6
7#pragma once
8
9#include <bit>
10#include <mutex>
11
12//! @brief The utility module.
13namespace utility // NOLINT(modernize-concat-nested-namespaces)
14{
15//! @brief Memory-pool-related functions in the utility module.
16namespace memory
17{
18//! @brief Brief function description.
19//! @return function description (module_function)
20inline static const char* description() noexcept
21{
22 return "UTIL_MEMORY";
23}
24extern const char* version() noexcept;
25
26//! @brief Memory pool.
27//! @tparam Res - type of resource to allocate
28//! @tparam BlockSize - size of the chunk's memory pool allocates
29template <typename Res, std::size_t BlockSize = 4096>
30class Memory
31{
32public:
33 //! @brief Construct a new Memory object.
34 Memory() = default;
35 //! @brief Destroy the Memory object.
36 virtual ~Memory();
37 //! @brief Construct a new Memory object.
38 Memory(const Memory&) = delete;
39 //! @brief Construct a new Memory object.
40 //! @tparam Other - type of resource to allocate
41 template <typename Other>
42 Memory(const Memory<Other>&) = delete;
43 //! @brief Construct a new Memory object.
44 //! @param memory - object for move constructor
45 Memory(Memory&& memory) noexcept;
46 //! @brief The operator (=) overloading of Memory class.
47 //! @return reference of the Memory object
48 Memory& operator=(const Memory&) = delete;
49 //! @brief The operator (=) overloading of Memory class.
50 //! @param memory - object for move assignment operator
51 //! @return reference of the Memory object
52 Memory& operator=(Memory&& memory) noexcept;
53
54 //! @brief New an element.
55 //! @tparam Args - type of arguments for constructing the resource
56 //! @param args - arguments for constructing the resource
57 //! @return pointer of the allocated resource
58 template <typename... Args>
59 Res* newEntry(Args&&... args);
60 //! @brief Delete an element.
61 //! @param res - pointer of the allocated resource
62 void deleteEntry(Res* const res);
63
64private:
65 //! @brief Union for the slot that stores element information.
66 union alignas(alignof(Res)) Slot
67 {
68 //! @brief Allocated resource.
69 Res element;
70 //! @brief Next pointer of the slot.
71 Slot* next;
72 };
73
74 //! @brief Pointer to the current block.
75 Slot* currentBlock{nullptr};
76 //! @brief Pointer to the current slot.
77 Slot* currentSlot{nullptr};
78 //! @brief Pointer to the last slot.
79 const Slot* lastSlot{nullptr};
80 //! @brief Pointer to the free slots.
81 Slot* freeSlots{nullptr};
82 //! @brief Mutex for controlling resource.
83 mutable std::recursive_mutex mtx;
84
85 //! @brief Construct the resource.
86 //! @tparam Alloc - type of allocated resource
87 //! @tparam Args - type of arguments for constructing the resource
88 //! @param alloc - pointer of the allocated resource
89 //! @param args - arguments for constructing the resource
90 template <typename Alloc, typename... Args>
91 void construct(Alloc* const alloc, Args&&... args);
92 //! @brief Destroy the resource.
93 //! @tparam Alloc - type of allocated resource
94 //! @param alloc - pointer of the allocated resource
95 template <typename Alloc>
96 void destroy(const Alloc* const alloc);
97 //! @brief Allocate resource.
98 //! @param size - resource size
99 //! @param hint - address hint to suggest where allocation could occur
100 //! @return pointer of the allocated resource
101 Res* allocate(const std::size_t size = 1, const Res* hint = nullptr);
102 //! @brief Deallocate resource.
103 //! @param res - pointer of the allocated resource
104 //! @param size - resource size
105 void deallocate(Res* const res, const std::size_t size = 1);
106 //! @brief Create the block.
107 void createBlock();
108 //! @brief Calculate the padding size for the pointer of data in the element.
109 //! @param data - pointer of data in the element
110 //! @param align - align size
111 //! @return padding size
112 std::size_t pointerPadding(const std::byte* const data, const std::size_t align) const;
113
114 static_assert(BlockSize >= (2 * sizeof(Slot)));
115};
116
117// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
118template <typename Res, std::size_t BlockSize>
119Memory<Res, BlockSize>::~Memory()
120{
121 auto* curr = currentBlock;
122 while (curr)
123 {
124 auto* const prev = curr->next;
125 operator delete(reinterpret_cast<void*>(curr));
126 curr = prev;
127 }
128}
129
130template <typename Res, std::size_t BlockSize>
131Memory<Res, BlockSize>::Memory(Memory&& memory) noexcept :
132 currentBlock{memory.currentBlock},
133 currentSlot{memory.currentSlot},
134 lastSlot{memory.lastSlot},
135 freeSlots{memory.freeSlots}
136{
137 memory.currentBlock = nullptr;
138 memory.currentSlot = nullptr;
139 memory.lastSlot = nullptr;
140 memory.freeSlots = nullptr;
141}
142
143template <typename Res, std::size_t BlockSize>
144Memory<Res, BlockSize>& Memory<Res, BlockSize>::operator=(Memory&& memory) noexcept
145{
146 if (&memory != this)
147 {
148 std::swap(currentBlock, memory.currentBlock);
149 std::swap(currentSlot, memory.currentSlot);
150 std::swap(lastSlot, memory.lastSlot);
151 std::swap(freeSlots, memory.freeSlots);
152 }
153 return *this;
154}
155
156template <typename Res, std::size_t BlockSize>
157template <typename... Args>
158Res* Memory<Res, BlockSize>::newEntry(Args&&... args)
159{
160 const std::lock_guard<std::recursive_mutex> lock(mtx);
161 auto* const res = allocate();
162 construct<Res>(res, std::forward<Args>(args)...);
163 return res;
164}
165
166template <typename Res, std::size_t BlockSize>
167void Memory<Res, BlockSize>::deleteEntry(Res* const res)
168{
169 const std::lock_guard<std::recursive_mutex> lock(mtx);
170 if (res)
171 {
172 destroy(res);
173 deallocate(res);
174 }
175}
176
177template <typename Res, std::size_t BlockSize>
178template <typename Alloc, typename... Args>
179void Memory<Res, BlockSize>::construct(Alloc* const alloc, Args&&... args)
180{
181 if (alloc)
182 {
183 std::construct_at(alloc, std::forward<Args>(args)...);
184 }
185}
186
187template <typename Res, std::size_t BlockSize>
188template <typename Alloc>
189void Memory<Res, BlockSize>::destroy(const Alloc* const alloc)
190{
191 if (alloc)
192 {
193 std::destroy_at(alloc);
194 }
195}
196
197template <typename Res, std::size_t BlockSize>
198Res* Memory<Res, BlockSize>::allocate(const std::size_t /*size*/, const Res* /*hint*/)
199{
200 if (freeSlots)
201 {
202 auto* const res = std::launder(reinterpret_cast<Res*>(freeSlots));
203 freeSlots = freeSlots->next;
204 return res;
205 }
206
207 if (!currentSlot || (currentSlot >= lastSlot))
208 {
209 createBlock();
210 }
211 return reinterpret_cast<Res*>(currentSlot++);
212}
213
214template <typename Res, std::size_t BlockSize>
215void Memory<Res, BlockSize>::deallocate(Res* const res, const std::size_t /*size*/)
216{
217 if (res)
218 {
219 reinterpret_cast<Slot*>(res)->next = freeSlots;
220 freeSlots = reinterpret_cast<Slot*>(res);
221 }
222}
223
224template <typename Res, std::size_t BlockSize>
225void Memory<Res, BlockSize>::createBlock()
226{
227 auto* const newBlock = reinterpret_cast<std::byte*>(operator new(BlockSize));
228 reinterpret_cast<Slot*>(newBlock)->next = currentBlock;
229 currentBlock = reinterpret_cast<Slot*>(newBlock);
230
231 auto* const body = std::next(x: newBlock, n: sizeof(Slot*));
232 const std::size_t offset = pointerPadding(data: body, align: alignof(Slot));
233 currentSlot = reinterpret_cast<Slot*>(body + offset);
234 lastSlot = reinterpret_cast<Slot*>(newBlock + BlockSize);
235}
236
237template <typename Res, std::size_t BlockSize>
238std::size_t Memory<Res, BlockSize>::pointerPadding(const std::byte* const data, const std::size_t align) const
239{
240 if (align != 0)
241 {
242 const auto padding = std::bit_cast<std::uintptr_t>(from: data);
243 return (align - (padding % align)) % align;
244 }
245 return 0;
246}
247// NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
248} // namespace memory
249} // namespace utility
250