| /* |
| * Copyright 2023 Rive |
| */ |
| |
| #pragma once |
| |
| #include "rive/math/math_types.hpp" |
| #include <cassert> |
| #include <memory> |
| #include <vector> |
| |
| namespace rive |
| { |
| // Fast block allocator for trivially-destructible types. |
| class TrivialBlockAllocator |
| { |
| public: |
| TrivialBlockAllocator(size_t initialBlockSize) : m_initialBlockSize(initialBlockSize) |
| { |
| m_blocks.push_back(std::unique_ptr<char[]>(new char[m_initialBlockSize])); |
| reset(); |
| } |
| |
| void reset() |
| { |
| m_fibMinus2 = 0; |
| m_fibMinus1 = 1; |
| m_blocks.resize(1); |
| m_currentBlockSize = m_initialBlockSize; |
| m_currentBlockUsage = 0; |
| } |
| |
| template <size_t AlignmentInBytes = 8> void* alloc(size_t sizeInBytes) |
| { |
| uintptr_t start = reinterpret_cast<uintptr_t>(m_blocks.back().get()) + m_currentBlockUsage; |
| size_t alignmentPad = math::round_up_to_multiple_of<AlignmentInBytes>(start) - start; |
| |
| // Ensure there is room for this allocation in our newest block, pushing a new one if |
| // needed. |
| if (m_currentBlockUsage + alignmentPad + sizeInBytes > m_currentBlockSize) |
| { |
| // Grow with a fibonacci function. |
| size_t fib = m_fibMinus2 + m_fibMinus1; |
| m_fibMinus2 = m_fibMinus1; |
| m_fibMinus1 = fib; |
| |
| size_t blockSize = |
| std::max(fib * m_initialBlockSize, sizeInBytes + AlignmentInBytes - 1); |
| m_blocks.push_back(std::unique_ptr<char[]>(new char[blockSize])); |
| m_currentBlockSize = blockSize; |
| m_currentBlockUsage = 0; |
| |
| start = reinterpret_cast<uintptr_t>(m_blocks.back().get()); |
| alignmentPad = math::round_up_to_multiple_of<AlignmentInBytes>(start) - start; |
| } |
| |
| char* ret = &m_blocks.back()[m_currentBlockUsage + alignmentPad]; |
| m_currentBlockUsage += alignmentPad + sizeInBytes; |
| assert((reinterpret_cast<uintptr_t>(ret) % AlignmentInBytes) == 0); |
| assert(ret + sizeInBytes <= m_blocks.back().get() + m_currentBlockSize); |
| return ret; |
| } |
| |
| void rewindLastAllocation(size_t rewindSizeInBytes) |
| { |
| assert(rewindSizeInBytes <= m_currentBlockUsage); |
| m_currentBlockUsage -= rewindSizeInBytes; |
| } |
| |
| template <typename T, typename... Args> T* make(Args&&... args) |
| { |
| // We don't call destructors on objects that get allocated here. We just free the blocks |
| // at the end. So objects must be trivially destructible. |
| static_assert(std::is_trivially_destructible<T>::value); |
| return new (alloc<alignof(T)>(sizeof(T))) T(std::forward<Args>(args)...); |
| } |
| |
| private: |
| const size_t m_initialBlockSize; |
| |
| // Grow block sizes using a fibonacci function. |
| size_t m_fibMinus2; |
| size_t m_fibMinus1; |
| |
| std::vector<std::unique_ptr<char[]>> m_blocks; |
| size_t m_currentBlockSize; |
| size_t m_currentBlockUsage; |
| }; |
| |
| // Basic array allocator for POD types, based on TrivialBlockAllocator. |
| template <typename T, size_t AlignmentInBytes = alignof(T)> |
| class TrivialArrayAllocator : private TrivialBlockAllocator |
| { |
| static_assert(std::is_pod<T>::value); |
| |
| public: |
| TrivialArrayAllocator(size_t initialCount) : TrivialBlockAllocator(initialCount * sizeof(T)) {} |
| |
| T* alloc(size_t count) |
| { |
| return reinterpret_cast<T*>(TrivialBlockAllocator::alloc(count * sizeof(T))); |
| } |
| |
| void rewindLastAllocation(size_t rewindCount) |
| { |
| TrivialBlockAllocator::rewindLastAllocation(rewindCount * sizeof(T)); |
| } |
| |
| using TrivialBlockAllocator::reset; |
| }; |
| |
| // Simple linked list whose nodes are allocated on a TrivialBlockAllocator. |
| template <typename T> class BlockAllocatedLinkedList |
| { |
| public: |
| size_t count() const |
| { |
| assert(static_cast<bool>(m_head) == static_cast<bool>(m_tail)); |
| assert(static_cast<bool>(m_head) == static_cast<bool>(m_tail)); |
| return m_count; |
| } |
| |
| bool empty() const { return count() == 0; } |
| |
| T& tail() const |
| { |
| assert(!empty()); |
| return m_tail->data; |
| } |
| |
| template <typename... Args> T& emplace_back(TrivialBlockAllocator& allocator, Args... args) |
| { |
| Node* node = allocator.make<Node>(std::forward<Args>(args)...); |
| assert(static_cast<bool>(m_head) == static_cast<bool>(m_tail)); |
| if (m_head == nullptr) |
| { |
| m_head = node; |
| } |
| else |
| { |
| m_tail->next = node; |
| } |
| m_tail = node; |
| ++m_count; |
| return m_tail->data; |
| } |
| |
| void reset() |
| { |
| m_tail = nullptr; |
| m_head = nullptr; |
| m_count = 0; |
| } |
| |
| struct Node |
| { |
| template <typename... Args> Node(Args... args) : data(std::forward<Args>(args)...) {} |
| T data; |
| Node* next = nullptr; |
| }; |
| |
| template <typename U> class Iter |
| { |
| public: |
| Iter(Node* current) : m_current(current) {} |
| bool operator!=(const Iter& other) const { return m_current != other.m_current; } |
| void operator++() { m_current = m_current->next; } |
| U& operator*() { return m_current->data; } |
| |
| private: |
| Node* m_current; |
| }; |
| Iter<T> begin() { return {m_head}; } |
| Iter<T> end() { return {nullptr}; } |
| Iter<const T> begin() const { return {m_head}; } |
| Iter<const T> end() const { return {nullptr}; } |
| |
| private: |
| Node* m_head = nullptr; |
| Node* m_tail = nullptr; |
| size_t m_count = 0; |
| }; |
| |
| } // namespace rive |