146 lines
4.1 KiB
C++
146 lines
4.1 KiB
C++
|
|
#pragma once
|
|
|
|
#if !defined(MIJIN_CONTAINER_CACHED_ARRAY_HPP_INCLUDED)
|
|
#define MIJIN_CONTAINER_CACHED_ARRAY_HPP_INCLUDED 1
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <limits>
|
|
#include <optional>
|
|
#include <vector>
|
|
#include "../debug/assert.hpp"
|
|
|
|
namespace mijin
|
|
{
|
|
|
|
//
|
|
// public defines
|
|
//
|
|
|
|
//
|
|
// public constants
|
|
//
|
|
|
|
//
|
|
// public types
|
|
//
|
|
|
|
template<typename T, std::size_t cacheSize = 32>
|
|
class CachedArray
|
|
{
|
|
public:
|
|
using element_type = T;
|
|
using value_type = std::remove_cv_t<T>;
|
|
using size_type = std::size_t;
|
|
using difference_type = std::ptrdiff_t;
|
|
using pointer = T*;
|
|
using const_pointer = const T*;
|
|
using reference = T&;
|
|
using const_reference = const T&;
|
|
using iterator = T*;
|
|
using const_iterator = const T*;
|
|
|
|
static constexpr std::size_t INVALID_INDEX = std::numeric_limits<size_type>::max();
|
|
private:
|
|
struct CacheEntry
|
|
{
|
|
size_type index = INVALID_INDEX;
|
|
std::size_t useIndex = 0;
|
|
std::optional<T> value;
|
|
};
|
|
mutable std::array<CacheEntry, cacheSize> cache_;
|
|
std::vector<std::optional<T>> backbuffer_;
|
|
std::size_t nextUseIndex_ = 1;
|
|
public:
|
|
[[nodiscard]] reference at(size_type index);
|
|
[[nodiscard]] const_reference at(size_type index) const;
|
|
[[nodiscard]] size_type size() const { return backbuffer_.size(); }
|
|
void resize(size_type newSize);
|
|
void reserve(size_type elements);
|
|
|
|
// TODO: make iterable (not that easy if we move from the backbuffer!)
|
|
// [[nodiscard]] iterator begin() { return &backbuffer_[0]; }
|
|
// [[nodiscard]] const_iterator begin() const { return &backbuffer_[0]; }
|
|
// [[nodiscard]] const_iterator cbegin() const { return &backbuffer_[0]; }
|
|
// [[nodiscard]] iterator end() { return begin() + size(); }
|
|
// [[nodiscard]] const_iterator end() const { return begin() + size(); }
|
|
// [[nodiscard]] const_iterator cend() const { return begin() + size(); }
|
|
|
|
[[nodiscard]] reference operator[](size_type index) { return at(index); }
|
|
[[nodiscard]] const_reference operator[](size_type index) const { return at(index); }
|
|
private:
|
|
// TODO: use deducing this once it is available
|
|
template<typename TSelf>
|
|
static auto atImpl(TSelf&& self, std::size_t index);
|
|
};
|
|
|
|
//
|
|
// public functions
|
|
//
|
|
|
|
template<typename T, std::size_t cacheSize>
|
|
auto CachedArray<T, cacheSize>::at(std::size_t index) -> reference
|
|
{
|
|
return atImpl(*this, index);
|
|
}
|
|
|
|
template<typename T, std::size_t cacheSize>
|
|
auto CachedArray<T, cacheSize>::at(std::size_t index) const -> const_reference
|
|
{
|
|
return atImpl(*this, index);
|
|
}
|
|
|
|
template<typename T, std::size_t cacheSize>
|
|
void CachedArray<T, cacheSize>::resize(size_type newSize)
|
|
{
|
|
if (newSize < size())
|
|
{
|
|
// invalidate any cache entries that shouldn't exist anymore
|
|
for (CacheEntry& entry : cache_)
|
|
{
|
|
if (entry.index < newSize)
|
|
{
|
|
entry.value = std::nullopt;
|
|
entry.useIndex = 0;
|
|
}
|
|
}
|
|
}
|
|
backbuffer_.resize(newSize);
|
|
}
|
|
|
|
template<typename T, std::size_t cacheSize>
|
|
template<typename TSelf>
|
|
auto CachedArray<T, cacheSize>::atImpl(TSelf&& self, std::size_t index)
|
|
{
|
|
MIJIN_ASSERT(index < size(), "CachedArray::at(): Attempting to access index out of range.");
|
|
// first try the cache
|
|
for (CacheEntry& entry : self.cache_)
|
|
{
|
|
if (entry.index == index)
|
|
{
|
|
entry.useIndex = self.nextUseIndex_++;
|
|
return *entry.value;
|
|
}
|
|
}
|
|
|
|
// otherwise copy from backbuffer to cache
|
|
auto itCache = std::min_element(self.cache_.begin(), self.cache_.end(), [](const CacheEntry& left, const CacheEntry& right) {
|
|
return left.useIndex < right.useIndex;
|
|
});
|
|
if (itCache->index != INVALID_INDEX)
|
|
{
|
|
// move back to the backbuffer
|
|
self.backbuffer_[itCache->index] = std::move(itCache->value);
|
|
}
|
|
itCache->index = index;
|
|
itCache->value = std::move(self.backbuffer_.at(index));
|
|
itCache->useIndex = self.nextUseIndex_++;
|
|
return *itCache->value;
|
|
}
|
|
|
|
} // namespace mijin
|
|
|
|
#endif // !defined(MIJIN_CONTAINER_CACHED_ARRAY_HPP_INCLUDED)
|