Line data Source code
1 0 : // Distributed under the MIT License. 2 : // See LICENSE.txt for details. 3 : 4 : #pragma once 5 : 6 : #include <array> 7 : #include <cstddef> 8 : #include <mutex> 9 : #include <optional> 10 : #include <type_traits> 11 : #include <utility> 12 : 13 : #include "Utilities/ErrorHandling/Error.hpp" 14 : #include "Utilities/Gsl.hpp" 15 : #include "Utilities/StaticCache.hpp" 16 : #include "Utilities/TypeTraits/IsInteger.hpp" 17 : 18 : /// \ingroup UtilitiesGroup 19 : /// A cache that selects and lazily initializes entries at runtime. 20 : /// 21 : /// Unlike `StaticCache`, this class does not instantiate the generator once for 22 : /// every combination of indices. This reduces compile time for large caches at 23 : /// the cost of runtime index calculation and synchronization. 24 : /// 25 : /// \note `RuntimeCache` is tested in `Test_StaticCache.cpp`. 26 : template <typename Generator, typename T, typename... Ranges> 27 1 : class RuntimeCache { 28 : public: 29 0 : explicit RuntimeCache(Generator generator) 30 : : generator_{std::move(generator)} {} 31 : 32 : template <typename... Args> 33 0 : const T& operator()(const Args... parameters) const { 34 : static_assert(sizeof...(parameters) == sizeof...(Ranges), 35 : "Number of arguments must match number of ranges."); 36 : size_t array_location = 0; 37 : ((array_location = array_location * static_cast<size_t>(Ranges::size) + 38 : index<Ranges>(parameters)), 39 : ...); 40 : std::call_once(gsl::at(initialized_, array_location), [this, array_location, 41 : ¶meters...]() { 42 : gsl::at(cached_objects_, array_location) 43 : .emplace(generator_( 44 : static_cast<typename Ranges::value_type>(parameters)...)); 45 : }); 46 : return gsl::at(cached_objects_, array_location).value(); 47 : } 48 : 49 : private: 50 : template <typename Range, typename U> 51 0 : static size_t index(const U parameter) { 52 : if constexpr (std::is_enum_v<U>) { 53 : static_assert( 54 : std::is_same_v<typename Range::value_type, std::remove_cv_t<U>>, 55 : "Mismatched enum parameter type and cached type."); 56 : for (size_t i = 0; i < Range::size; ++i) { 57 : if (parameter == gsl::at(Range::values, i)) { 58 : return i; 59 : } 60 : } 61 : ERROR("Uncached enumeration value: " << parameter); 62 : } else { 63 : static_assert( 64 : tt::is_integer_v<std::remove_cv_t<U>>, 65 : "The parameter passed for a CacheRange must be an integer type."); 66 : if (UNLIKELY( 67 : Range::start > static_cast<decltype(Range::start)>(parameter) or 68 : static_cast<decltype(Range::start)>(parameter) >= Range::end)) { 69 : ERROR("Index out of range: " << Range::start << " <= " << parameter 70 : << " < " << Range::end); 71 : } 72 : return static_cast<size_t>( 73 : static_cast<typename Range::value_type>(parameter) - Range::start); 74 : } 75 : } 76 : 77 0 : static constexpr size_t number_of_entries = 78 : (size_t{1} * ... * static_cast<size_t>(Ranges::size)); 79 0 : Generator generator_; 80 : // NOLINTNEXTLINE(spectre-mutable) 81 0 : mutable std::array<std::once_flag, number_of_entries> initialized_{}; 82 : // NOLINTNEXTLINE(spectre-mutable) 83 0 : mutable std::array<std::optional<T>, number_of_entries> cached_objects_{}; 84 : }; 85 : 86 : /// \ingroup UtilitiesGroup 87 : /// Create a RuntimeCache, inferring the cached type from the generator. 88 : template <typename... Ranges, typename Generator> 89 1 : auto make_runtime_cache(Generator&& generator) { 90 : using CachedType = std::remove_cvref_t<decltype(generator( 91 : std::declval<typename Ranges::value_type>()...))>; 92 : return RuntimeCache<std::remove_cvref_t<Generator>, CachedType, Ranges...>( 93 : std::forward<Generator>(generator)); 94 : }