Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
common.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/assert.hpp>
4
5#include <algorithm>
6#include <cstddef>
7#include <cstdint>
8#include <limits>
9#include <string_view>
10
11namespace helios::mem {
12
13/// @brief Default alignment used by memory resources.
14inline constexpr size_t kDefaultAlignment = 64;
15
16/// @brief Minimum alignment used by memory resources.
17inline constexpr size_t kMinAlignment = alignof(std::max_align_t);
18
19/**
20 * @brief Saturating add for `size_t`.
21 * @param lhs Left operand
22 * @param rhs Right operand
23 * @return lhs + rhs, clamped to `size_t` max on overflow
24 */
25[[nodiscard]] constexpr size_t SaturatingAdd(size_t lhs, size_t rhs) noexcept {
26 constexpr size_t kMax = std::numeric_limits<size_t>::max();
27 if (rhs > kMax - lhs) {
28 return kMax;
29 }
30 return lhs + rhs;
31}
32
33/**
34 * @brief Saturating multiply for `size_t`.
35 * @param lhs Left operand
36 * @param rhs Right operand
37 * @return lhs * rhs, clamped to `size_t` max on overflow
38 */
39[[nodiscard]] constexpr size_t SaturatingMul(size_t lhs, size_t rhs) noexcept {
40 constexpr size_t kMax = std::numeric_limits<size_t>::max();
41 if (lhs == 0 || rhs == 0) {
42 return 0;
43 }
44 if (lhs > kMax / rhs) {
45 return kMax;
46 }
47 return lhs * rhs;
48}
49
50/// @brief Growth strategy used by growable allocators.
51enum class GrowthMode : uint8_t {
52 kLinear, ///< Adds a fixed byte step on each growth request.
53 kGeometric, ///< Multiplies capacity by a configured ratio on growth.
54};
55
56/**
57 * @brief Capacity growth configuration.
58 * @details This policy is shared by all PMR allocators in the memory module.
59 */
61 /// @brief Active growth strategy.
63 /// @brief Bytes added per growth step when `mode == kLinear`.
64 size_t linear_step = 0;
65 /// @brief Capacity multiplier numerator for geometric growth.
67 /// @brief Capacity multiplier denominator for geometric growth.
69 /// @brief Maximum capacity ceiling for any growth operation.
70 size_t max_capacity = std::numeric_limits<size_t>::max();
71
72 /**
73 * @brief Creates a linear growth policy.
74 * @param step Bytes added per growth step
75 * @param max Maximum capacity ceiling
76 * @return Linear growth policy
77 */
78 [[nodiscard]] static constexpr GrowthPolicy Linear(
79 size_t step, size_t max = std::numeric_limits<size_t>::max()) noexcept {
80 return {
81 .mode = GrowthMode::kLinear,
82 .linear_step = step,
83 .geometric_numerator = 2,
84 .geometric_denominator = 1,
85 .max_capacity = max,
86 };
87 }
88
89 /**
90 * @brief Creates a geometric growth policy.
91 * @param numerator Capacity multiplier numerator
92 * @param denominator Capacity multiplier denominator
93 * @param max Maximum capacity ceiling
94 * @return Geometric growth policy
95 */
96 [[nodiscard]] static constexpr GrowthPolicy Geometric(
97 size_t numerator = 2, size_t denominator = 1,
98 size_t max = std::numeric_limits<size_t>::max()) noexcept {
99 return {
101 .linear_step = 0,
102 .geometric_numerator = numerator,
103 .geometric_denominator = denominator,
104 .max_capacity = max,
105 };
106 }
107
108 /**
109 * @brief Computes the next block capacity for a grow request.
110 * @param current_capacity Current block or region capacity
111 * @param min_capacity Minimum bytes required for the pending allocation
112 * @return Next capacity, clamped to `max_capacity`; returns `0` when growth
113 * is disabled
114 */
115 [[nodiscard]] constexpr size_t NextCapacity(
116 size_t current_capacity, size_t min_capacity) const noexcept {
117 if (current_capacity >= min_capacity) {
118 return current_capacity;
119 }
120
121 size_t next = current_capacity;
122 if (next == 0) {
123 next = kMinAlignment;
124 }
125
126 if (mode == GrowthMode::kLinear) {
127 if (linear_step == 0) {
128 return 0;
129 }
130 while (next < min_capacity) {
131 const size_t previous = next;
132 next = SaturatingAdd(next, linear_step);
133 if (next <= previous) {
134 next = std::numeric_limits<size_t>::max();
135 }
136 if (next == std::numeric_limits<size_t>::max()) {
137 break;
138 }
139 }
140 } else {
141 if (geometric_denominator == 0 ||
143 return 0;
144 }
145 while (next < min_capacity) {
146 const size_t previous = next;
147 const size_t scaled = SaturatingMul(next, geometric_numerator);
148 next = scaled / geometric_denominator;
149 if (next <= previous) {
150 next = std::numeric_limits<size_t>::max();
151 }
152 if (next == std::numeric_limits<size_t>::max()) {
153 break;
154 }
155 }
156 }
157
158 return std::min(next, max_capacity);
159 }
160};
161
162/// @brief Runtime statistics snapshot for PMR allocators.
164 /// @brief Bytes currently reserved or bumped (allocator-specific semantics).
165 size_t total_allocated = 0;
166 /// @brief High-water mark of `total_allocated`.
167 size_t peak_usage = 0;
168 /// @brief Number of live allocations tracked by the allocator.
170 /// @brief Total successful allocation calls.
172 /// @brief Total deallocation calls (including arena no-op deallocates).
174 /// @brief Padding bytes consumed for alignment.
175 size_t alignment_waste = 0;
176};
177
178/// @brief Error codes used for explicit non-fatal memory operations.
179enum class MemoryError : uint8_t {
180 kOutOfMemory, ///< Allocation request could not be satisfied.
181 kInvalidAlignment, ///< Requested alignment is not supported.
182 kInvalidSize, ///< Requested size is zero or overflows.
183 kGrowthDisabled, ///< Growable allocator cannot expand further.
184 kOwnershipMismatch, ///< Pointer does not belong to the allocator.
185};
186
187/**
188 * @brief Converts MemoryError to a readable string.
189 * @param error Error value
190 * @return String view with the error description
191 */
192[[nodiscard]] constexpr std::string_view MemoryErrorToString(
193 MemoryError error) noexcept {
194 switch (error) {
196 return "Out of memory";
198 return "Invalid alignment";
200 return "Invalid size";
202 return "Growth is disabled";
204 return "Pointer is not owned by allocator";
205 default:
206 return "Unknown memory error";
207 }
208}
209
210/**
211 * @brief Checks if a value is a power of two.
212 * @param value Value to check
213 * @return True if value is a power of two
214 */
215[[nodiscard]] constexpr bool IsPowerOfTwo(size_t value) noexcept {
216 return value != 0 && (value & (value - 1)) == 0;
217}
218
219/**
220 * @brief Aligns value up to alignment.
221 * @warning Triggers assertion when `alignment` is not a power of two.
222 * @param value Value to align
223 * @param alignment Alignment, must be a power of two
224 * @return Aligned value
225 */
226[[nodiscard]] constexpr size_t AlignUp(size_t value,
227 size_t alignment) noexcept {
228 HELIOS_ASSERT(IsPowerOfTwo(alignment), "alignment '{}' must be power of two!",
229 alignment);
230 const size_t mask = alignment - 1;
231 if (value > std::numeric_limits<size_t>::max() - mask) {
232 return std::numeric_limits<size_t>::max();
233 }
234 return (value + mask) & ~mask;
235}
236
237/**
238 * @brief Aligns pointer up to alignment.
239 * @warning Triggers assertion when `alignment` is not a power of two.
240 * @param ptr Pointer to align
241 * @param alignment Alignment, must be a power of two
242 * @return Aligned pointer
243 */
244[[nodiscard]] inline void* AlignUpPtr(void* ptr, size_t alignment) noexcept {
245 HELIOS_ASSERT(IsPowerOfTwo(alignment), "alignment '{}' must be power of two!",
246 alignment);
247 auto* const raw = static_cast<std::byte*>(ptr);
248 const auto addr = reinterpret_cast<uintptr_t>(raw);
249 const auto aligned = AlignUp(addr, alignment);
250 return reinterpret_cast<void*>(aligned);
251}
252
253/**
254 * @brief Checks if pointer is aligned.
255 * @warning Triggers assertion when `alignment` is not a power of two.
256 * @param ptr Pointer value
257 * @param alignment Alignment to check
258 * @return True if pointer is aligned
259 */
260[[nodiscard]] inline bool IsAligned(const void* ptr,
261 size_t alignment) noexcept {
262 HELIOS_ASSERT(IsPowerOfTwo(alignment), "alignment '{}' must be power of two",
263 alignment);
264 const auto addr = reinterpret_cast<uintptr_t>(ptr);
265 return (addr & (alignment - 1)) == 0;
266}
267
268/**
269 * @brief Computes padding required for pointer alignment.
270 * @param ptr Current pointer
271 * @param alignment Requested alignment
272 * @return Padding in bytes
273 */
274[[nodiscard]] inline size_t CalculatePadding(const void* ptr,
275 size_t alignment) noexcept {
276 const auto addr = reinterpret_cast<uintptr_t>(ptr);
277 const auto aligned = AlignUp(addr, alignment);
278 return aligned - addr;
279}
280
281/**
282 * @brief Computes padding for aligned storage with a prepended header.
283 * @param ptr Current pointer
284 * @param alignment Requested alignment
285 * @param header_size Header size stored before aligned user memory
286 * @return Padding in bytes, including header space
287 */
288[[nodiscard]] inline size_t CalculatePaddingWithHeader(
289 const void* ptr, size_t alignment, size_t header_size) noexcept {
290 size_t padding = CalculatePadding(ptr, alignment);
291 if (padding < header_size) {
292 padding = SaturatingAdd(padding, AlignUp(header_size - padding, alignment));
293 }
294 return padding;
295}
296
297} // namespace helios::mem
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:259
GrowthMode
Growth strategy used by growable allocators.
Definition common.hpp:51
@ kGeometric
Multiplies capacity by a configured ratio on growth.
Definition common.hpp:53
@ kLinear
Adds a fixed byte step on each growth request.
Definition common.hpp:52
constexpr bool IsPowerOfTwo(size_t value) noexcept
Checks if a value is a power of two.
Definition common.hpp:215
constexpr size_t AlignUp(size_t value, size_t alignment) noexcept
Aligns value up to alignment.
Definition common.hpp:226
constexpr size_t kMinAlignment
Minimum alignment used by memory resources.
Definition common.hpp:17
constexpr std::string_view MemoryErrorToString(MemoryError error) noexcept
Converts MemoryError to a readable string.
Definition common.hpp:192
size_t CalculatePaddingWithHeader(const void *ptr, size_t alignment, size_t header_size) noexcept
Computes padding for aligned storage with a prepended header.
Definition common.hpp:288
constexpr size_t SaturatingMul(size_t lhs, size_t rhs) noexcept
Saturating multiply for size_t.
Definition common.hpp:39
bool IsAligned(const void *ptr, size_t alignment) noexcept
Checks if pointer is aligned.
Definition common.hpp:260
size_t CalculatePadding(const void *ptr, size_t alignment) noexcept
Computes padding required for pointer alignment.
Definition common.hpp:274
MemoryError
Error codes used for explicit non-fatal memory operations.
Definition common.hpp:179
@ kInvalidAlignment
Requested alignment is not supported.
Definition common.hpp:181
@ kOwnershipMismatch
Pointer does not belong to the allocator.
Definition common.hpp:184
@ kGrowthDisabled
Growable allocator cannot expand further.
Definition common.hpp:183
@ kOutOfMemory
Allocation request could not be satisfied.
Definition common.hpp:180
@ kInvalidSize
Requested size is zero or overflows.
Definition common.hpp:182
void * AlignUpPtr(void *ptr, size_t alignment) noexcept
Aligns pointer up to alignment.
Definition common.hpp:244
constexpr size_t kDefaultAlignment
Default alignment used by memory resources.
Definition common.hpp:14
constexpr size_t SaturatingAdd(size_t lhs, size_t rhs) noexcept
Saturating add for size_t.
Definition common.hpp:25
Runtime statistics snapshot for PMR allocators.
Definition common.hpp:163
size_t total_allocated
Bytes currently reserved or bumped (allocator-specific semantics).
Definition common.hpp:165
size_t peak_usage
High-water mark of total_allocated.
Definition common.hpp:167
size_t total_deallocations
Total deallocation calls (including arena no-op deallocates).
Definition common.hpp:173
size_t alignment_waste
Padding bytes consumed for alignment.
Definition common.hpp:175
size_t total_allocations
Total successful allocation calls.
Definition common.hpp:171
size_t allocation_count
Number of live allocations tracked by the allocator.
Definition common.hpp:169
Capacity growth configuration.
Definition common.hpp:60
static constexpr GrowthPolicy Linear(size_t step, size_t max=std::numeric_limits< size_t >::max()) noexcept
Creates a linear growth policy.
Definition common.hpp:78
constexpr size_t NextCapacity(size_t current_capacity, size_t min_capacity) const noexcept
Computes the next block capacity for a grow request.
Definition common.hpp:115
static constexpr GrowthPolicy Geometric(size_t numerator=2, size_t denominator=1, size_t max=std::numeric_limits< size_t >::max()) noexcept
Creates a geometric growth policy.
Definition common.hpp:96
size_t geometric_numerator
Capacity multiplier numerator for geometric growth.
Definition common.hpp:66
size_t linear_step
Bytes added per growth step when mode == kLinear.
Definition common.hpp:64
size_t geometric_denominator
Capacity multiplier denominator for geometric growth.
Definition common.hpp:68
GrowthMode mode
Active growth strategy.
Definition common.hpp:62
size_t max_capacity
Maximum capacity ceiling for any growth operation.
Definition common.hpp:70