459 lines
15 KiB
C++
459 lines
15 KiB
C++
#pragma once
|
|
|
|
#include <charconv>
|
|
#include <initializer_list>
|
|
#include <Vector.h>
|
|
#include <unordered_map>
|
|
#include "Base.h"
|
|
#include <game_sa/Timer.h>
|
|
|
|
namespace notsa {
|
|
//template<typename TChar, size_t N>
|
|
//struct basic_static_string {
|
|
// template<typename YChar>
|
|
// friend std::strong_ordering operator<=>(const basic_static_string<YChar>& self, std::basic_string_view<YChar> sv) {
|
|
// sv.compare(std::basic_string_view<YChar>{m_chars});
|
|
// }
|
|
//
|
|
//private:
|
|
// TChar m_chars[N]{};
|
|
//};
|
|
|
|
namespace rng = std::ranges;
|
|
|
|
namespace detail {
|
|
template<typename K, typename V, size_t N>
|
|
struct Mapping {
|
|
using value_type = std::pair<const K, const V>;
|
|
using storage_type = std::array<value_type, N>;
|
|
using iterator = storage_type::iterator;
|
|
using const_iterator = storage_type::const_iterator;
|
|
|
|
constexpr Mapping(value_type (&&m)[N]) :
|
|
m_mapping{std::to_array(m)}
|
|
{
|
|
}
|
|
|
|
constexpr const_iterator find(auto&& needle) const { // Using auto&& instead of K&& to allow transparent lookup
|
|
for (auto it = begin(); it != end(); it++) {
|
|
if (it->first == needle) {
|
|
return it;
|
|
}
|
|
}
|
|
return end();
|
|
}
|
|
|
|
constexpr auto begin() const { return m_mapping.begin(); }
|
|
constexpr auto end() const { return m_mapping.end(); }
|
|
|
|
protected:
|
|
storage_type m_mapping;
|
|
};
|
|
};
|
|
|
|
/*!
|
|
* @brief Create a mapping of k-v pairs. Dynamically chooses between an unordered_map and a custom fixed-size mapping.
|
|
*/
|
|
template<typename K, typename V, size_t N>
|
|
auto make_mapping(std::pair<const K, const V> (&&m)[N]) {
|
|
if constexpr (N > 10) { // After 10 or so elements unordered_map becomes faster
|
|
return std::unordered_map<K, V>{std::begin(m), std::end(m)};
|
|
} else { // Otherwise the stack allocated one is faster
|
|
return detail::Mapping<K, V, N>{std::move(m)};
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* @brief Helper function to get kv-mapping value from a key.
|
|
* @brief Unlike `.find()`, this returns the value directly
|
|
*/
|
|
constexpr inline auto find_value_or(auto&& mapping, auto&& needle, auto&& defval) {
|
|
const auto it = mapping.find(needle);
|
|
return it != mapping.end()
|
|
? it->second
|
|
: defval;
|
|
}
|
|
|
|
/*!
|
|
* @brief Helper function to get kv-mapping value from a key.
|
|
* @brief Unlike `.find()`, this returns the value directly, or asserts if the key is not found.
|
|
*/
|
|
constexpr inline auto find_value(auto&& mapping, auto&& needle) {
|
|
const auto it = mapping.find(needle);
|
|
if (it != mapping.end()) {
|
|
return it->second;
|
|
}
|
|
NOTSA_UNREACHABLE("Needle not in the mapping!");
|
|
}
|
|
|
|
/*!
|
|
* @brief Find the index of the first occurrence of a value in a range, returning a default index if not found.
|
|
*/
|
|
template<rng::input_range R>
|
|
ptrdiff_t indexof(R&& r, const rng::range_value_t<R>& v, ptrdiff_t defaultIdx = -1) {
|
|
const auto it = rng::find(r, v);
|
|
return it != rng::end(r)
|
|
? rng::distance(rng::begin(r), it)
|
|
: defaultIdx;
|
|
}
|
|
|
|
//! [mostly] Works like C#'s `??` (null coalescing operator) or GCC's `?:`
|
|
template<typename T>
|
|
T coalesce(T a, T b) {
|
|
return a ? a : b;
|
|
}
|
|
|
|
/*!
|
|
* Much like std::stoi [and variants] but takes an `std::string_view` + in debug does error checking [unlike the C stuff]
|
|
* @param str The string to convert
|
|
* @param radix The radix (base) of the number
|
|
* @param end The end of the the number in the string (points to inside `sv`)
|
|
*/
|
|
template<std::integral T>
|
|
T ston(std::string_view str, int radix = 10, const char** end = nullptr) {
|
|
T out;
|
|
const auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), out, radix);
|
|
assert(ec == std::errc{});
|
|
if (end) {
|
|
*end = ptr;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/*!
|
|
* Much like std::stof [and variants] but takes an `std::string_view` + in debug does error checking [unlike the C stuff]
|
|
* @param str The string to convert
|
|
* @param fmt The formatting mode
|
|
* @param end The end of the the number in the string (points to inside `sv`)
|
|
*/
|
|
template<typename T>
|
|
requires std::is_floating_point_v<T>
|
|
T ston(std::string_view str, std::chars_format fmt = std::chars_format::general, const char** end = nullptr) {
|
|
T out;
|
|
const auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), out, fmt);
|
|
assert(ec != std::errc{});
|
|
if (end) {
|
|
*end = ptr;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/*
|
|
* Want to know something funny?
|
|
* `std::initializer_list` is just a proxy object for a stack allocated array.
|
|
* So, if you return one from a function you're dommed to be fucked :)
|
|
* And best thing, it does allow copying, it has a fucking copy constructor for whatever reason
|
|
* Lesson: Don't return `initializer_list`'s from functions
|
|
*/
|
|
|
|
/*!
|
|
* @brief Call the given function on object destruction.
|
|
*/
|
|
template<typename Fn>
|
|
struct ScopeGuard {
|
|
ScopeGuard(Fn fn) :
|
|
m_fn{ std::move(fn) }
|
|
{
|
|
}
|
|
|
|
~ScopeGuard() {
|
|
std::invoke(m_fn);
|
|
}
|
|
|
|
private:
|
|
Fn m_fn;
|
|
};
|
|
|
|
constexpr auto IsFixBugs() {
|
|
#ifdef FIX_BUGS
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
template<rng::input_range R, typename T_Ptr = rng::range_value_t<R>>
|
|
requires std::is_pointer_v<T_Ptr>
|
|
auto SpatialQuery(R&& r, CVector distToPos, T_Ptr ignored, T_Ptr closest = nullptr) {
|
|
const auto GetDistSq = [distToPos](T_Ptr e) {
|
|
return (e->GetPosition() - distToPos).SquaredMagnitude();
|
|
};
|
|
|
|
float closestDistSq = closest
|
|
? GetDistSq(closest)
|
|
: std::numeric_limits<float>::max();
|
|
for (T_Ptr e : r) {
|
|
if (ignored && e == ignored) {
|
|
continue;
|
|
}
|
|
const auto distSq = GetDistSq(e);
|
|
if (closestDistSq > distSq) {
|
|
closestDistSq = distSq;
|
|
closest = e;
|
|
}
|
|
}
|
|
|
|
struct Ret{ T_Ptr entity; float distSq; };
|
|
return Ret{ closest, closestDistSq };
|
|
}
|
|
|
|
/// Predicate to check if `value` is null
|
|
struct IsNull {
|
|
template<typename T>
|
|
requires(std::is_pointer_v<T>)
|
|
bool operator()(T ptr) { return ptr == nullptr; }
|
|
};
|
|
//template<typename T>
|
|
// requires(std::is_pointer_v<T>)
|
|
//bool IsNull(T value) { return value == nullptr; }
|
|
|
|
/// Negate another predicate function
|
|
template<typename T>
|
|
auto Not(bool(*fn)(T)) { return [fn](const T& value) { return !fn(value); }; }
|
|
|
|
struct NotIsNull {
|
|
template<typename T>
|
|
bool operator()(const T* ptr) {
|
|
return ptr != nullptr;
|
|
}
|
|
};
|
|
|
|
//! Find first non-null value in range. If found it's returned, `null` otherwise.
|
|
template<rng::input_range R, typename T_Ret = rng::range_value_t<R>>
|
|
requires(std::is_pointer_v<T_Ret>)
|
|
T_Ret FirstNonNull(R&& range) {
|
|
const auto it = rng::find_if(range, NotIsNull{});
|
|
return it != rng::end(range)
|
|
? *it
|
|
: nullptr;
|
|
}
|
|
|
|
// https://stackoverflow.com/a/52667105
|
|
template <typename T, std::size_t... Ds>
|
|
struct mdarray_impl;
|
|
|
|
template <typename T, std::size_t D>
|
|
struct mdarray_impl<T, D> {
|
|
using type = std::array<T, D>;
|
|
};
|
|
|
|
template <typename T, std::size_t D, std::size_t... Ds>
|
|
struct mdarray_impl<T, D, Ds...> {
|
|
using type = std::array<typename mdarray_impl<T, Ds...>::type, D>;
|
|
};
|
|
|
|
//! Multidimensional array - Represents a C array of with dimensions in the same order as specified here
|
|
template <typename T, std::size_t... Ds>
|
|
using mdarray = typename mdarray_impl<T, Ds...>::type;
|
|
|
|
/*!
|
|
* @arg value The value to search for in the range
|
|
*
|
|
* @brief Check if a range contains a value, uses `rng::find`. NOTE: If you plan on using the iterator, just use `rng::find` instead..
|
|
*/
|
|
template<rng::input_range R, class T = rng::range_value_t<R>, class Proj = std::identity>
|
|
requires std::indirect_binary_predicate<rng::equal_to, std::projected<rng::iterator_t<R>, Proj>, const T*>
|
|
bool contains(R&& r, const T& value, Proj proj = {}) {
|
|
return rng::find(r, value, proj) != rng::end(r);
|
|
}
|
|
|
|
|
|
/*!
|
|
* Helper (Of your fingers) - Reduces typing needed for Python style `value in {}`
|
|
*/
|
|
template<typename Y>
|
|
bool contains(std::initializer_list<Y> r, const Y& value) {
|
|
return contains(r, value, {});
|
|
}
|
|
|
|
/*!
|
|
* @brief Similar to `std::remove_if`, but only removes the first element found (Unlike the former that removes all)
|
|
*
|
|
* @return Whenever an element was removed. If it was, you have to pop the last element from your container
|
|
*/
|
|
template <std::permutable I, std::sentinel_for<I> S, class T, class Proj = std::identity>
|
|
requires std::indirect_binary_predicate<rng::equal_to, std::projected<I, Proj>, const T*>
|
|
constexpr bool remove_first(I first, S last, const T& value, Proj proj = {}) {
|
|
first = rng::find(std::move(first), last, value, proj);
|
|
if (first == last) {
|
|
return false;
|
|
}
|
|
else {
|
|
rng::move_backward(rng::next(first), last, std::prev(last)); // Shift to the left (removing the found element)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// We require `bidirectional_range`, because we have to use `std::prev`.
|
|
// if for any reason we want to use `forward_range` only, I guess we gotta figure
|
|
// out a different way of getting the pre-end iteartor
|
|
|
|
//! @copydoc `remove_first`
|
|
template <rng::bidirectional_range R, class T, class Proj = std::identity>
|
|
requires std::permutable<rng::iterator_t<R>>&& std::indirect_binary_predicate<rng::equal_to, std::projected<rng::iterator_t<R>, Proj>, const T*>
|
|
constexpr bool remove_first(R&& r, const T& value, Proj proj = {}) {
|
|
return remove_first(rng::begin(r), rng::end(r), value, std::move(proj));
|
|
}
|
|
|
|
//! `std::ranges` like `accumulate` function => Hopefully to be replaced by an `std` implementation.
|
|
template<rng::input_range R, typename T, typename FnOp = std::plus<>, class Proj = std::identity>
|
|
T accumulate(R&& r, T init, Proj proj = {}, FnOp op = {}) {
|
|
for (const auto& v : r) {
|
|
init = std::invoke(op, init, std::invoke(proj, v));
|
|
}
|
|
return init;
|
|
}
|
|
|
|
//! Same as rng::min, but accepts a default value that is returned in case the range is empty (Which would result be UB for `rng::min`)
|
|
template<rng::forward_range R, typename Pr = std::less<>, class Proj = std::identity>
|
|
constexpr rng::range_value_t<R> min_default(R&& r, rng::range_value_t<R> defaultValue, Pr pr = {}, Proj proj = {}) {
|
|
if (rng::empty(r)) {
|
|
return std::move(defaultValue);
|
|
}
|
|
return rng::min(r, pr, proj);
|
|
}
|
|
|
|
/*!
|
|
* @brief Helper functor - to be used as projection to `ranges` functions - to cast a value into another type.
|
|
*
|
|
* @tparam O - Type to cast to
|
|
*/
|
|
template<typename O>
|
|
struct cast_to {
|
|
template<typename I>
|
|
O operator()(I&& input) {
|
|
return static_cast<O>(input);
|
|
}
|
|
};
|
|
|
|
|
|
/*!
|
|
* @tparam Start The number at which to start the iteration
|
|
* @tparam Stop The number at which to stop the iteration
|
|
* @tparam ChunkSize The chunk size, see function description.
|
|
*
|
|
* @arg functor The functor instance that has a `operator()` with matching `template<size_t>`. Templated lambdas can be used.
|
|
*
|
|
* This function will iterate thru the sequence of numbers in range [Base, Stop), and
|
|
* call the functor's templated `operator()` with the index being the first template argument.
|
|
*
|
|
* @code{.cpp}
|
|
* constexpr auto Bar = []<size_t Idx>(){
|
|
* std::cout << Idx << ", ";
|
|
* };
|
|
* IterateFunction<0, 5>(Bar); // Prints: 0, 1, 2, 3, 4
|
|
* @endcode
|
|
*
|
|
* About the `ChunkSize` template parameter:
|
|
* We have to balance between recursion and index seq. size, because:
|
|
* - Fold op. by default maxes out at ~256 arguments (MSVC)
|
|
* - contexpr recursion is limited to ~1000 (MSVC)
|
|
* So we can't rely on using only 1 method, gotta use both at the same time
|
|
* In case you ever run into one of the limits:
|
|
* - Fold op. => Decrease `ChunkSize`
|
|
* - Recursion => Increase `ChunkSize`
|
|
*/
|
|
template<int Start, int Stop, int ChunkSize = 128>
|
|
static constexpr void IterateFunction(auto&& functor) {
|
|
// Invoke function with current sequence
|
|
[&] <std::size_t... Idx>(std::index_sequence<Idx...>) {
|
|
(functor.template operator()<Start + Idx>(), ...);
|
|
}(std::make_index_sequence<std::min(ChunkSize, Stop - Start)>{});
|
|
|
|
// Continue recursing if there's anything left
|
|
if constexpr (Stop - Start > ChunkSize) {
|
|
IterateFunction<Start + ChunkSize, Stop, ChunkSize>(functor);
|
|
}
|
|
}
|
|
|
|
template<typename T, typename... Ts>
|
|
concept is_any_of_type_v = (std::same_as<T, Ts> || ...);
|
|
|
|
//! Check if the type is an integer type excluding bool and character types.
|
|
template<typename T>
|
|
inline constexpr bool is_standard_integer = std::is_integral_v<T> && !is_any_of_type_v<T, bool, char, wchar_t, char8_t, char16_t, char32_t>;
|
|
|
|
//! Null terminated `std::format_to`. Use inplace of sprintf.
|
|
//! NOTE: Not a complete replacement for std::format_to,
|
|
//! e.g. it doesn't use output iterators. i don't care.
|
|
template<size_t N, class... Args>
|
|
void format_to_sz(char(&out)[N], std::string_view fmt, Args&&... args) {
|
|
*std::vformat_to(out, fmt, std::make_format_args(args...)) = '\0';
|
|
}
|
|
|
|
//! Reads a pointer as specified type.
|
|
template<typename T> requires std::is_trivially_constructible_v<T>
|
|
T ReadAs(void* ptr) {
|
|
return *static_cast<T*>(ptr);
|
|
}
|
|
//! Safe C string copying, use this instead of strcpy.
|
|
inline void string_copy(char* out, const char* from, size_t size) {
|
|
std::snprintf(out, size, "%s", from);
|
|
}
|
|
|
|
//! Safe C string copying, use this instead of strcpy.
|
|
template<size_t N>
|
|
void string_copy(char (&out)[N], const char* from) {
|
|
std::snprintf(out, N, "%s", from);
|
|
}
|
|
|
|
// Like clamp, but wraps the number - https://stackoverflow.com/a/64273069/15363969
|
|
template<std::floating_point F>
|
|
F wrap(F x, F min, F max) {
|
|
if (min > max) {
|
|
std::swap(min, max);
|
|
}
|
|
return (x < 0 ? max : min) + std::fmod(x, max - min);
|
|
}
|
|
|
|
template<std::floating_point F>
|
|
F step_up_to(F x, F to, F step, bool useTimeStep = false) {
|
|
return std::min<F>(to, x + (useTimeStep ? std::pow(step, CTimer::GetTimeStep()) : step));
|
|
}
|
|
|
|
template<std::floating_point F>
|
|
F step_down_to(F x, F to, F step, bool useTimeStep = false) {
|
|
return std::max<F>(to, x - (useTimeStep ? std::pow(step, CTimer::GetTimeStep()) : step));
|
|
}
|
|
|
|
/*!
|
|
* @brief Step `x` towards `to` in steps
|
|
* @brief This function is useful for interpolating changing values
|
|
* @arg stepUp The step size when `x` is less than `to`
|
|
* @arg stepDown The step size when `x` is greater than `to`
|
|
* @arg useTimeStep Whether to use timestep for calculating the correct step size (Avoids FPS related issues somewhat)
|
|
*********/
|
|
template<std::floating_point F>
|
|
F step_to(F x, F to, F stepUp, F stepDown, bool useTimeStep = false) {
|
|
assert(stepDown > 0.f);
|
|
assert(stepUp > 0.f);
|
|
|
|
if (x == to) {
|
|
return to;
|
|
}
|
|
return x < to
|
|
? step_up_to<F>(x, to, stepUp, useTimeStep)
|
|
: step_down_to<F>(x, to, stepUp, useTimeStep);
|
|
}
|
|
|
|
// Step `x` towards `to` in steps
|
|
template<std::floating_point F>
|
|
F step_to(F x, F to, F step, bool useTimeStep = false) {
|
|
return step_to<F>(x, to, step, step, useTimeStep);
|
|
}
|
|
|
|
//! Range of a specific type
|
|
//! See: https://stackoverflow.com/a/64228354/15363969
|
|
template <typename R, typename T>
|
|
concept range_of = rng::range<R> && std::same_as<rng::range_value_t<R>, T>;
|
|
|
|
// https://www.reddit.com/r/cpp/comments/1hw6a29/comment/m61d6wv
|
|
template <class T, template <typename...> class Template>
|
|
concept is_specialization_of = requires ( std::remove_cvref_t<T> t )
|
|
{
|
|
// Check an immediately invoked lambda can compile
|
|
[]<typename... Args> ( Template<Args...>& ) {} ( t );
|
|
};
|
|
|
|
}; // namespace notsa
|