// ===================================================================================================================== // fennec, a free and open source game engine // Copyright (C) 2025 Medusa Slockbower // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // ===================================================================================================================== /// /// \file allocator.h /// \brief This header contains structures and classes related to allocating blocks of memory /// /// /// \details /// \author Medusa Slockbower /// /// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)) /// /// #ifndef FENNEC_MEMORY_ALLOCATOR_H #define FENNEC_MEMORY_ALLOCATOR_H #include #include #include #include #include #include #include #include #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wchanges-meaning" #endif namespace fennec { /// /// \brief Helper structure for obtaining traits of an allocator class /// \tparam Alloc The allocator class to analyze template struct allocator_traits { private: // These help with using concepts in `detect_t` template using __pointer = typename ClassT::pointer_t; template using __const_pointer = typename ClassT::const_pointer_t; template using __void_pointer = typename ClassT::void_pointer_t; template using __void_const_pointer = typename ClassT::void_const_pointer_t; // Propagation Patterns template using __propagate_on_containter_copy_assignment = typename ClassT::propagate_on_containter_copy_assignment; template using __propagate_on_containter_move_assignment = typename ClassT::propagate_on_containter_move_assignment; template using __propagate_on_containter_swap = typename ClassT::propagate_on_containter_swap; template using __is_always_equal = typename ClassT::is_always_equal; template struct __rebind : replace_first_element {}; template requires requires { typename AllocT::template rebind::other; } struct __rebind { using type = typename AllocT::template rebind::other; }; // This detects AllocT::diff_t if present, otherwise uses the diff_t associated with PtrT // It works using SFINAE, 'typename = void' forces the second __diff to be evaluated first when // __diff is substituted using only two template arguments. That one uses 'type = typename AllocT::diff_t' // however, if it fails, the compiler moves on to the original definition. __size works in the same manner. template struct __diff { using type = typename ptr_traits::diff_t; }; template struct __diff> { using type = typename AllocT::diff_t; }; template struct __size : make_unsigned {}; template struct __size> { using type = typename AllocT::size_t; }; public: /// \brief Alias for the allocator type using alloc_t = Alloc; /// \brief Alias for the value type of the allocator using value_t = typename Alloc::value_t; /// \brief Alias for a pointer to the value type. Will use `Alloc::pointer_t` if present using pointer_t = detect_t; /// \brief Alias for a const pointer to the value type. Will use `Alloc::const_pointer_t` if present using const_pointer_t = detect_t; /// \brief Alias for a pointer to void. Will use `Alloc::void_pointer_t` if present using void_pointer_t = detect_t; /// \brief Alias for a const pointer to void. Will use `Alloc::const_void_pointer_t` if present using const_void_pointer_t = detect_t; /// \brief Alias for differences between pointers. Will use `Alloc::diff_t` if present using diff_t = typename __diff::type; /// \brief Alias for the size of allocations. Will use `Alloc::size_t` if present using size_t = typename __size::type; // TODO: Document propagation using propagate_on_container_copy_assignment = detect_t; using propagate_on_container_move_assignment = detect_t; using propagate_on_container_swap = detect_t; /// \brief Checks if this allocator type is always equal to another allocator of similar type using is_always_equal = detect_t; /// \brief Rebinds the allocator type to produce an element type of type `TypeT` template using rebind = typename __rebind::type; // TODO: allocator_traits static functions }; /// /// \brief Allocator implementation, uses `new` and `delete` operators. /// \tparam T The data type to allocate template class allocator { public: /// \brief Alias for the data type used for metaprogramming using value_t = T; /// \brief Metaprogramming utility to rebind an allocator to a different data type template using rebind = allocator; /// \brief Default Constructor constexpr allocator() = default; /// \brief Default Destructor constexpr ~allocator() = default; /// \brief Copy Constructor constexpr allocator(const allocator&) = default; /// \brief Copy Assignment constexpr allocator& operator=(const allocator&) = default; /// \brief Equality operator constexpr bool_t operator==(const allocator&) { return true; } /// \brief Inequality operator constexpr bool_t operator!=(const allocator&) { return false; } /// \brief Equality operator for allocators of same type but with different data type template constexpr bool_t operator==(const allocator&) { return true; } /// \brief Inequality operator for allocators of same type but with different data type template constexpr bool_t operator!=(const allocator&) { return true; } /// \brief Allocate a block of memory large enough to hold `n` elements of type `T` constexpr T* allocate(size_t n) { return static_cast(::operator new(n * sizeof(T))); } /// \brief Allocate a block of memory large enough to hold `n` elements of type `T` constexpr T* allocate(size_t n, align_t align) { return static_cast(::operator new(n * sizeof(T), align)); } /// \brief Deallocate a block of memory with type `T` constexpr void deallocate(T* ptr) { return ::operator delete(ptr); } /// \brief Deallocate a block of memory with type `T` constexpr void deallocate(T* ptr, align_t align) { return ::operator delete(ptr, align); } }; /// /// \brief Allocator implementation, uses `new` and `delete` operators. /// \tparam T The data type to allocate template class allocator { public: /// \brief Alias for the data type used for metaprogramming using value_t = T; /// \brief Metaprogramming utility to rebind an allocator to a different data type template using rebind = allocator; /// \brief Default Constructor constexpr allocator() = default; /// \brief Default Destructor constexpr ~allocator() = default; /// \brief Copy Constructor constexpr allocator(const allocator&) = default; /// \brief Copy Assignment constexpr allocator& operator=(const allocator&) = default; /// \brief Equality operator constexpr bool_t operator==(const allocator&) { return true; } /// \brief Inequality operator constexpr bool_t operator!=(const allocator&) { return false; } /// \brief Equality operator for allocators of same type but with different data type template constexpr bool_t operator==(const allocator&) { return true; } /// \brief Inequality operator for allocators of same type but with different data type template constexpr bool_t operator!=(const allocator&) { return true; } /// \brief Allocate a block of memory large enough to hold `n` elements of type `T` constexpr T* allocate(size_t n) { return static_cast(::operator new[](n * sizeof(T))); } /// \brief Allocate a block of memory large enough to hold `n` elements of type `T` constexpr T* allocate(size_t n, align_t align) { return static_cast(::operator new[](n * sizeof(T), align)); } /// \brief Deallocate a block of memory with type `T` constexpr void deallocate(T* ptr) { return ::operator delete[](ptr); } /// \brief Deallocate a block of memory with type `T` constexpr void deallocate(T* ptr, align_t align) { return ::operator delete[](ptr, align); } }; /// /// \brief Container to hold a memory allocation /// \tparam T The data type of the allocation /// /// \details This simply acts as a proxy for allocating memory. It does not call any constructors or /// initialize any values as if they were the provided data type. Any operations present work /// only on individual bytes. template struct allocation { public: /// \brief alias for the allocator type using alloc_t = typename allocator_traits::template rebind; /// \brief alias for the data type using value_t = T; /// \brief size type definition for ptr_traits using size_t = size_t; /// \brief diff type definition for ptr_traits using diff_t = ptrdiff_t; // Cosntructors ======================================================================================================== /// /// \brief Default Constructor, initializes internal data to `null` and the capacity to `0` constexpr allocation() noexcept : _data(nullptr), _capacity(0) { } /// /// \brief Sized Constructor, initializes the allocation with a block of size `n * sizeof(T)` bytes /// \param n The number of elements of type `T` to allocate for constexpr allocation(size_t n) noexcept : _data(nullptr), _capacity(0) { allocate(n); } /// /// \brief Buffer Copy Constructor, initializes the allocation with a block of size `n * sizeof(T)` bytes. /// Then, the contents of data are copied into the allocation. /// \param data the buffer to copy /// \param n the number of elements constexpr allocation(const T* data, size_t n) : allocation(n) { fennec::memcpy(_data, data, n); } /// /// \brief Sized Constructor, initializes the allocation with a block of size `n * sizeof(T)` bytes /// \param n The number of elements of type `T` to allocate for constexpr allocation(size_t n, align_t align) noexcept : _data(nullptr) , _capacity(0) , _alignment(zero()) { allocate(n, align); } /// /// \brief Buffer Copy Constructor, initializes the allocation with a block of size `n * sizeof(T)` bytes. /// Then, the contents of data are copied into the allocation. /// \param data the buffer to copy /// \param n the number of elements constexpr allocation(const T* data, size_t n, align_t align) : allocation(n, align) { fennec::memcpy(_data, data, n); } /// /// \brief Allocator Constructor /// \param alloc The allocation object to copy. /// /// \details This constructor should be used when the type `AllocT` needs internal data. constexpr allocation(const alloc_t& alloc) noexcept : _alloc(alloc) , _data(nullptr) , _capacity(0) { } /// /// \brief Sized Allocator Constructor /// \param n The number of elements of type `T` to allocate for /// \param alloc The allocation object to copy. /// /// \details This constructor should be used when the type `AllocT` needs internal data. constexpr allocation(size_t n, const alloc_t& alloc) noexcept : _alloc(alloc) , _data(nullptr) , _capacity(0) { allocate(n); } /// /// \brief Buffer Copy Allocator Constructor, initializes the allocation with a block of size `n * sizeof(T)` bytes. /// Then, the contents of data are copied into the allocation. /// \param data the buffer to copy /// \param n the number of elements /// \param alloc The allocation object to copy. /// /// \details This constructor should be used when the type `AllocT` needs internal data. constexpr allocation(const T* data, size_t n, const alloc_t& alloc) : allocation(n, alloc) { fennec::memcpy(_data, data, n); } /// /// \brief Sized Allocator Constructor /// \param n The number of elements of type `T` to allocate for /// \param alloc The allocation object to copy. /// /// \details This constructor should be used when the type `AllocT` needs internal data. constexpr allocation(size_t n, align_t align, const alloc_t& alloc) noexcept : _alloc(alloc) , _data(nullptr) , _capacity(0) , _alignment(zero()) { allocate(n, align); } /// /// \brief Buffer Copy Allocator Constructor, initializes the allocation with a block of size `n * sizeof(T)` bytes. /// Then, the contents of data are copied into the allocation. /// \param data the buffer to copy /// \param n the number of elements /// \param alloc The allocation object to copy. /// /// \details This constructor should be used when the type `AllocT` needs internal data. constexpr allocation(const T* data, size_t n, align_t align, const alloc_t& alloc) : allocation(n, align, alloc) { fennec::memcpy(_data, data, n); } /// /// \brief Copy Constructor, creates an allocation of equal size and performs a byte-wise copy /// \param alloc The allocation to copy constexpr allocation(const allocation& alloc) noexcept : _alloc(alloc._alloc) , _data(_alloc.allocate(alloc._capacity)) , _capacity(alloc._capacity) { fennec::memcpy(_data, alloc._data, alloc._capacity * sizeof(T)); } /// /// \brief Move Constructor, moves the data in `alloc` to the new object and cleans `alloc` so that it /// can safely destruct /// \param alloc The allocation to move constexpr allocation(allocation&& alloc) noexcept : _alloc(alloc._alloc) , _data(alloc._data) , _capacity(alloc._capacity) { alloc._data = nullptr; alloc._capacity = 0; } /// /// \brief Default Destructor, releases the memory block if still present constexpr ~allocation() noexcept { if (_data) _alloc.deallocate(_data); } // Assignment ========================================================================================================== /// /// \brief Copy Assignment Operator /// \param alloc the allocation to copy /// \returns a reference to `this` constexpr allocation& operator=(const allocation& alloc) { allocation::allocate(alloc.capacity()); fennec::memcpy(_data, alloc, size()); return *this; } /// /// \brief Move Assignment Operator /// \param alloc the allocation to copy /// \returns a reference to `this` constexpr allocation& operator=(allocation&& alloc) noexcept { // Copy contents _alloc = alloc._alloc; _data = alloc._data; _capacity = alloc._capacity; // Cleanup alloc alloc._data = nullptr; alloc._capacity = 0; return *this; } // Allocation and Deallocation ========================================================================================= /// /// \brief Allocate a block of memory for the allocation. /// If there is already an allocated block of memory, the previous allocation is released. /// \param n The number of elements of type `T` to allocate for constexpr void allocate(size_t n, align_t align = zero()) noexcept { deallocate(); if (_alignment != zero()) { _data = _alloc.allocate(_capacity = n, _alignment = align); } else { _data = _alloc.allocate(_capacity = n); } } /// /// \brief Release the block of memory. constexpr void deallocate() noexcept { if (_data) { if (_alignment != zero()) { _alloc.deallocate(_data, _alignment); } else { _alloc.deallocate(_data); } } _data = nullptr; _capacity = 0; _alignment = zero(); } /// /// \brief Reallocate the block with a new size. /// Contents are copied to the new allocation. constexpr void reallocate(size_t n, align_t align = zero()) noexcept { if (_data == nullptr) { allocate(n, align); } value_t* old = _data; _data = _alloc.allocate(n); fennec::memcpy(_data, old, min(_capacity, n) * sizeof(T)); _alloc.deallocate(old); _capacity = n; } // Access ============================================================================================================== constexpr value_t& operator[](size_t i) { assertd(i < size(), "Array Out of Bounds"); return _data[i]; } constexpr const value_t operator[](size_t i) const { assertd(i < size(), "Array Out of Bounds"); return _data[i]; } // Modification ======================================================================================================== /// /// \brief Clear the block of memory, setting all bytes to 0. constexpr void clear() noexcept { fennec::memset(_data, 0, _capacity * sizeof(T)); } /// /// \brief Getter for the byte size of the allocation. /// \returns the size of the allocation in bytes constexpr size_t size() const { return _capacity * sizeof(T); } /// /// \brief Getter for the number of elements `n` of type `T` that the allocation can hold. /// \returns the size of the allocation in elements constexpr size_t capacity() const { return _capacity; } /// /// \brief Getter for the real pointer to the allocated block of memory /// \returns Pointer to the allocated memory. constexpr value_t* data() { return _data; } /// /// \brief Getter for the real pointer to the allocated block of memory /// \returns Pointer to the allocated memory. constexpr const value_t* data() const { return _data; } private: alloc_t _alloc; // Allocator object value_t* _data; // Handle for the memory block size_t _capacity; // Capacity of the memory block in elements. align_t _alignment; // Alignment information }; #ifdef __GNUC__ #pragma GCC diagnostic pop #endif } #endif // FENNEC_MEMORY_ALLOCATOR_H