6004 lines
172 KiB
C++
6004 lines
172 KiB
C++
// SPDX-License-Identifier: MIT
|
|
// SPDX-FileCopyrightText: 2025 Niels Martignène <niels.martignene@protonmail.com>
|
|
|
|
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <cmath>
|
|
#include <condition_variable>
|
|
#include <errno.h>
|
|
#include <float.h>
|
|
#include <functional>
|
|
#include <inttypes.h>
|
|
#include <limits.h>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <shared_mutex>
|
|
#if __cplusplus >= 202002L && __has_include(<source_location>)
|
|
#include <source_location>
|
|
#endif
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <thread>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#if defined(_WIN32)
|
|
#if !defined(STDIN_FILENO)
|
|
#define STDIN_FILENO 0
|
|
#endif
|
|
#if !defined(STDOUT_FILENO)
|
|
#define STDOUT_FILENO 1
|
|
#endif
|
|
#if !defined(STDERR_FILENO)
|
|
#define STDERR_FILENO 2
|
|
#endif
|
|
|
|
#include <intrin.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
#if defined(_MSC_VER)
|
|
#define ENABLE_INTSAFE_SIGNED_FUNCTIONS
|
|
#include <intsafe.h>
|
|
#pragma intrinsic(_BitScanReverse)
|
|
#if defined(_WIN64)
|
|
#pragma intrinsic(_BitScanReverse64)
|
|
#endif
|
|
#if defined(_M_IX86) || defined(_M_X64)
|
|
#pragma intrinsic(__rdtsc)
|
|
#endif
|
|
#endif
|
|
|
|
struct sigaction;
|
|
struct BrotliEncoderStateStruct;
|
|
|
|
namespace K {
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Config
|
|
// ------------------------------------------------------------------------
|
|
|
|
#if !defined(NDEBUG)
|
|
#define K_DEBUG
|
|
#endif
|
|
|
|
#define K_DEFAULT_ALLOCATOR MallocAllocator
|
|
#define K_BLOCK_ALLOCATOR_DEFAULT_SIZE Kibibytes(4)
|
|
|
|
#define K_HEAPARRAY_BASE_CAPACITY 8
|
|
#define K_HEAPARRAY_GROWTH_FACTOR 2.0
|
|
|
|
// Must be a power-of-two
|
|
#define K_HASHTABLE_BASE_CAPACITY 8
|
|
#define K_HASHTABLE_MAX_LOAD_FACTOR 0.5
|
|
|
|
#define K_FMT_STRING_BASE_CAPACITY 256
|
|
#define K_FMT_STRING_PRINT_BUFFER_SIZE 1024
|
|
|
|
#define K_LINE_READER_STEP_SIZE 65536
|
|
|
|
#define K_ASYNC_MAX_THREADS 2048
|
|
#define K_ASYNC_MAX_IDLE_TIME 10000
|
|
#define K_ASYNC_MAX_PENDING_TASKS 2048
|
|
|
|
#define K_PROGRESS_MAX_NODES 400
|
|
#define K_PROGRESS_USED_NODES 100
|
|
#define K_PROGRESS_TEXT_SIZE 64
|
|
|
|
#define K_COMPLETE_PATH_LIMIT 256
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Utility
|
|
// ------------------------------------------------------------------------
|
|
|
|
class StreamReader;
|
|
class StreamWriter;
|
|
|
|
extern "C" const char *FelixTarget;
|
|
extern "C" const char *FelixVersion;
|
|
extern "C" const char *FelixCompiler;
|
|
|
|
extern StreamReader *const StdIn;
|
|
extern StreamWriter *const StdOut;
|
|
extern StreamWriter *const StdErr;
|
|
|
|
#if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64) || __riscv_xlen == 64 || defined(__loongarch64)
|
|
typedef int64_t Size;
|
|
#define K_SIZE_MAX INT64_MAX
|
|
#elif defined(_WIN32) || defined(__APPLE__) || defined(__unix__)
|
|
typedef int32_t Size;
|
|
#define K_SIZE_MAX INT32_MAX
|
|
#elif defined(__thumb__) || defined(__arm__) || defined(__wasm32__)
|
|
typedef int32_t Size;
|
|
#define K_SIZE_MAX INT32_MAX
|
|
#else
|
|
#error Machine architecture not supported
|
|
#endif
|
|
|
|
#if defined(_MSC_VER) || __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
|
// Sane platform
|
|
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
|
#define K_BIG_ENDIAN
|
|
#else
|
|
#error This code base is not designed to support platforms with crazy endianness
|
|
#endif
|
|
|
|
#if UINT_MAX != 0xFFFFFFFFu
|
|
#error This code base is not designed to support non-32-bits int types
|
|
#endif
|
|
#if ULLONG_MAX != 0xFFFFFFFFFFFFFFFFull
|
|
#error This code base is not designed to support non-64-bits long long types
|
|
#endif
|
|
static_assert(sizeof(double) == 8, "This code base is not designed to support single-precision double floats");
|
|
|
|
#define K_STRINGIFY_(a) #a
|
|
#define K_STRINGIFY(a) K_STRINGIFY_(a)
|
|
#define K_CONCAT_(a, b) a ## b
|
|
#define K_CONCAT(a, b) K_CONCAT_(a, b)
|
|
#define K_UNIQUE_NAME(prefix) K_CONCAT(prefix, __LINE__)
|
|
#define K_FORCE_EXPAND(x) x
|
|
#define K_IGNORE (void)!
|
|
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
#define K_PUSH_NO_WARNINGS \
|
|
_Pragma("GCC diagnostic push") \
|
|
_Pragma("GCC diagnostic ignored \"-Wall\"") \
|
|
_Pragma("GCC diagnostic ignored \"-Wextra\"") \
|
|
_Pragma("GCC diagnostic ignored \"-Wconversion\"") \
|
|
_Pragma("GCC diagnostic ignored \"-Wsign-conversion\"") \
|
|
_Pragma("GCC diagnostic ignored \"-Wunused-function\"") \
|
|
_Pragma("GCC diagnostic ignored \"-Wunused-variable\"") \
|
|
_Pragma("GCC diagnostic ignored \"-Wunused-but-set-variable\"") \
|
|
_Pragma("GCC diagnostic ignored \"-Wunused-parameter\"") \
|
|
_Pragma("GCC diagnostic ignored \"-Wzero-as-null-pointer-constant\"") \
|
|
_Pragma("GCC diagnostic ignored \"-Winvalid-offsetof\"")
|
|
#define K_POP_NO_WARNINGS \
|
|
_Pragma("GCC diagnostic pop")
|
|
|
|
#if !defined(SCNd8)
|
|
#define SCNd8 "hhd"
|
|
#endif
|
|
#if !defined(SCNi8)
|
|
#define SCNi8 "hhd"
|
|
#endif
|
|
#if !defined(SCNu8)
|
|
#define SCNu8 "hhu"
|
|
#endif
|
|
#elif defined(_MSC_VER)
|
|
#define K_PUSH_NO_WARNINGS __pragma(warning(push, 0))
|
|
#define K_POP_NO_WARNINGS __pragma(warning(pop))
|
|
|
|
#define __restrict__ __restrict
|
|
#else
|
|
#error Compiler not supported
|
|
#endif
|
|
|
|
#if defined(__clang__)
|
|
#if __has_feature(address_sanitizer)
|
|
#define __SANITIZE_ADDRESS__
|
|
#endif
|
|
#if __has_feature(thread_sanitizer)
|
|
#define __SANITIZE_THREAD__
|
|
#endif
|
|
#if __has_feature(undefined_sanitizer)
|
|
#define __SANITIZE_UNDEFINED__
|
|
#endif
|
|
#endif
|
|
|
|
extern "C" void AssertMessage(const char *filename, int line, const char *cond);
|
|
|
|
#if defined(_MSC_VER)
|
|
#define K_DEBUG_BREAK() __debugbreak()
|
|
#elif defined(__clang__)
|
|
#define K_DEBUG_BREAK() __builtin_debugtrap()
|
|
#elif defined(__i386__) || defined(__x86_64__)
|
|
#define K_DEBUG_BREAK() __asm__ __volatile__("int $0x03")
|
|
#elif defined(__thumb__)
|
|
#define K_DEBUG_BREAK() __asm__ __volatile__(".inst 0xde01")
|
|
#elif defined(__aarch64__)
|
|
#define K_DEBUG_BREAK() __asm__ __volatile__(".inst 0xd4200000")
|
|
#elif defined(__arm__)
|
|
#define K_DEBUG_BREAK() __asm__ __volatile__(".inst 0xe7f001f0")
|
|
#elif defined(__riscv)
|
|
#define K_DEBUG_BREAK() __asm__ __volatile__("ebreak")
|
|
#elif defined(__loongarch64)
|
|
#define K_DEBUG_BREAK() __asm__ __volatile__("break 1")
|
|
#endif
|
|
|
|
#if defined(_MSC_VER) || __EXCEPTIONS
|
|
#define K_BAD_ALLOC() \
|
|
do { \
|
|
throw std::bad_alloc(); \
|
|
} while (false)
|
|
#else
|
|
#define K_BAD_ALLOC() \
|
|
do { \
|
|
PrintLn(StdErr, "Memory allocation failed"); \
|
|
abort(); \
|
|
} while (false);
|
|
#endif
|
|
|
|
#define K_CRITICAL(Cond, ...) \
|
|
do { \
|
|
if (!(Cond)) [[unlikely]] { \
|
|
PrintLn(StdErr, __VA_ARGS__); \
|
|
abort(); \
|
|
} \
|
|
} while (false)
|
|
#if defined(K_DEBUG)
|
|
#define K_ASSERT(Cond) \
|
|
do { \
|
|
if (!(Cond)) [[unlikely]] { \
|
|
K::AssertMessage(__FILE__, __LINE__, K_STRINGIFY(Cond)); \
|
|
K_DEBUG_BREAK(); \
|
|
abort(); \
|
|
} \
|
|
} while (false)
|
|
#else
|
|
#define K_ASSERT(Cond) \
|
|
do { \
|
|
(void)sizeof(Cond); \
|
|
} while (false)
|
|
#endif
|
|
|
|
#if defined(K_DEBUG)
|
|
#define K_UNREACHABLE() \
|
|
do { \
|
|
K::AssertMessage(__FILE__, __LINE__, "Reached code marked as UNREACHABLE"); \
|
|
K_DEBUG_BREAK(); \
|
|
abort(); \
|
|
} while (false)
|
|
#elif defined(__GNUC__) || defined(__clang__)
|
|
#define K_UNREACHABLE() __builtin_unreachable()
|
|
#else
|
|
#define K_UNREACHABLE() __assume(0)
|
|
#endif
|
|
|
|
#define K_DELETE_COPY(Cls) \
|
|
Cls(const Cls&) = delete; \
|
|
Cls &operator=(const Cls&) = delete;
|
|
|
|
constexpr uint16_t MakeUInt16(uint8_t high, uint8_t low)
|
|
{ return (uint16_t)(((uint16_t)high << 8) | low); }
|
|
constexpr uint32_t MakeUInt32(uint16_t high, uint16_t low) { return ((uint32_t)high << 16) | low; }
|
|
constexpr uint64_t MakeUInt64(uint32_t high, uint32_t low) { return ((uint64_t)high << 32) | low; }
|
|
|
|
constexpr Size Mebibytes(Size len) { return len * 1024 * 1024; }
|
|
constexpr Size Kibibytes(Size len) { return len * 1024; }
|
|
constexpr Size Megabytes(Size len) { return len * 1000 * 1000; }
|
|
constexpr Size Kilobytes(Size len) { return len * 1000; }
|
|
|
|
#define K_SIZE(Type) ((K::Size)sizeof(Type))
|
|
template <typename T, unsigned N>
|
|
char (&ComputeArraySize(T const (&)[N]))[N];
|
|
#define K_BITS(Type) (8 * K_SIZE(Type))
|
|
#define K_LEN(Array) K_SIZE(K::ComputeArraySize(Array))
|
|
|
|
static constexpr inline uint16_t ReverseBytes(uint16_t u)
|
|
{
|
|
return (uint16_t)(((u & 0x00FF) << 8) |
|
|
((u & 0xFF00) >> 8));
|
|
}
|
|
|
|
static constexpr inline uint32_t ReverseBytes(uint32_t u)
|
|
{
|
|
return ((u & 0x000000FF) << 24) |
|
|
((u & 0x0000FF00) << 8) |
|
|
((u & 0x00FF0000) >> 8) |
|
|
((u & 0xFF000000) >> 24);
|
|
}
|
|
|
|
static constexpr inline uint64_t ReverseBytes(uint64_t u)
|
|
{
|
|
return ((u & 0x00000000000000FF) << 56) |
|
|
((u & 0x000000000000FF00) << 40) |
|
|
((u & 0x0000000000FF0000) << 24) |
|
|
((u & 0x00000000FF000000) << 8) |
|
|
((u & 0x000000FF00000000) >> 8) |
|
|
((u & 0x0000FF0000000000) >> 24) |
|
|
((u & 0x00FF000000000000) >> 40) |
|
|
((u & 0xFF00000000000000) >> 56);
|
|
}
|
|
|
|
static constexpr inline int16_t ReverseBytes(int16_t i)
|
|
{ return (int16_t)ReverseBytes((uint16_t)i); }
|
|
static constexpr inline int32_t ReverseBytes(int32_t i)
|
|
{ return (int32_t)ReverseBytes((uint32_t)i); }
|
|
static constexpr inline int64_t ReverseBytes(int64_t i)
|
|
{ return (int64_t)ReverseBytes((uint64_t)i); }
|
|
|
|
#if defined(K_BIG_ENDIAN)
|
|
template <typename T>
|
|
constexpr T LittleEndian(T v) { return ReverseBytes(v); }
|
|
template <typename T>
|
|
constexpr T BigEndian(T v) { return v; }
|
|
#else
|
|
template <typename T>
|
|
constexpr T LittleEndian(T v) { return v; }
|
|
template <typename T>
|
|
constexpr T BigEndian(T v) { return ReverseBytes(v); }
|
|
#endif
|
|
|
|
#if defined(__GNUC__)
|
|
static inline int CountLeadingZeros(uint32_t u)
|
|
{
|
|
if (!u)
|
|
return 32;
|
|
|
|
return __builtin_clz(u);
|
|
}
|
|
static inline int CountLeadingZeros(uint64_t u)
|
|
{
|
|
if (!u)
|
|
return 64;
|
|
|
|
#if UINT64_MAX == ULONG_MAX
|
|
return __builtin_clzl(u);
|
|
#elif UINT64_MAX == ULLONG_MAX
|
|
return __builtin_clzll(u);
|
|
#else
|
|
#error Neither unsigned long nor unsigned long long is a 64-bit unsigned integer
|
|
#endif
|
|
}
|
|
|
|
static inline int CountTrailingZeros(uint32_t u)
|
|
{
|
|
if (!u)
|
|
return 32;
|
|
|
|
return __builtin_ctz(u);
|
|
}
|
|
static inline int CountTrailingZeros(uint64_t u)
|
|
{
|
|
if (!u)
|
|
return 64;
|
|
|
|
#if UINT64_MAX == ULONG_MAX
|
|
return __builtin_ctzl(u);
|
|
#elif UINT64_MAX == ULLONG_MAX
|
|
return __builtin_ctzll(u);
|
|
#else
|
|
#error Neither unsigned long nor unsigned long long is a 64-bit unsigned integer
|
|
#endif
|
|
}
|
|
|
|
static inline int PopCount(uint32_t u)
|
|
{
|
|
return __builtin_popcount(u);
|
|
}
|
|
static inline int PopCount(uint64_t u)
|
|
{
|
|
return __builtin_popcountll(u);
|
|
}
|
|
#elif defined(_MSC_VER)
|
|
static inline int CountLeadingZeros(uint32_t u)
|
|
{
|
|
unsigned long leading_zero;
|
|
if (_BitScanReverse(&leading_zero, u)) {
|
|
return (int)(31 - leading_zero);
|
|
} else {
|
|
return 32;
|
|
}
|
|
}
|
|
static inline int CountLeadingZeros(uint64_t u)
|
|
{
|
|
unsigned long leading_zero;
|
|
#if defined(_WIN64)
|
|
if (_BitScanReverse64(&leading_zero, u)) {
|
|
return (int)(63 - leading_zero);
|
|
} else {
|
|
return 64;
|
|
}
|
|
#else
|
|
if (_BitScanReverse(&leading_zero, u >> 32)) {
|
|
return (int)(31 - leading_zero);
|
|
} else if (_BitScanReverse(&leading_zero, (uint32_t)u)) {
|
|
return (int)(63 - leading_zero);
|
|
} else {
|
|
return 64;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static inline int CountTrailingZeros(uint32_t u)
|
|
{
|
|
unsigned long trailing_zero;
|
|
if (_BitScanForward(&trailing_zero, u)) {
|
|
return (int)trailing_zero;
|
|
} else {
|
|
return 32;
|
|
}
|
|
}
|
|
static inline int CountTrailingZeros(uint64_t u)
|
|
{
|
|
unsigned long trailing_zero;
|
|
#if defined(_WIN64)
|
|
if (_BitScanForward64(&trailing_zero, u)) {
|
|
return (int)trailing_zero;
|
|
} else {
|
|
return 64;
|
|
}
|
|
#else
|
|
if (_BitScanForward(&trailing_zero, (uint32_t)u)) {
|
|
return trailing_zero;
|
|
} else if (_BitScanForward(&trailing_zero, u >> 32)) {
|
|
return 32 + trailing_zero;
|
|
} else {
|
|
return 64;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static inline int PopCount(uint32_t u)
|
|
{
|
|
#if defined(_M_ARM64)
|
|
uint32_t count;
|
|
|
|
u = u - ((u >> 1) & 0x55555555);
|
|
u = (u & 0x33333333) + ((u >> 2) & 0x33333333);
|
|
count = ((u + (u >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
|
|
|
|
return (int)count;
|
|
#else
|
|
return __popcnt(u);
|
|
#endif
|
|
}
|
|
static inline int PopCount(uint64_t u)
|
|
{
|
|
#if defined(_M_X64)
|
|
return (int)__popcnt64(u);
|
|
#else
|
|
int count = PopCount((uint32_t)(u >> 32)) + PopCount((uint32_t)u);
|
|
return count;
|
|
#endif
|
|
}
|
|
#else
|
|
#error No implementation of CountLeadingZeros(), CountTrailingZeros() and PopCount() for this compiler / toolchain
|
|
#endif
|
|
|
|
#if __cplusplus >= 202002L && (defined(_MSC_VER) || defined(__clang__) || __GNUC__ >= 11)
|
|
#define K_CONSTINIT constinit
|
|
#else
|
|
#define K_CONSTINIT const
|
|
#endif
|
|
|
|
static inline Size AlignLen(Size len, Size align)
|
|
{
|
|
Size aligned = (len + align - 1) / align * align;
|
|
return aligned;
|
|
}
|
|
|
|
template <typename T>
|
|
static inline T *AlignUp(T *ptr, Size align)
|
|
{
|
|
uint8_t *aligned = (uint8_t *)(((uintptr_t)ptr + align - 1) / align * align);
|
|
return (T *)aligned;
|
|
}
|
|
|
|
template <typename T>
|
|
static inline T *AlignDown(T *ptr, Size align)
|
|
{
|
|
uint8_t *aligned = (uint8_t *)((uintptr_t)ptr / align * align);
|
|
return (T *)aligned;
|
|
}
|
|
|
|
// Calling memcpy (and friends) with a NULL source pointer is undefined behavior
|
|
// even if length is 0. This is dumb, work around this.
|
|
static inline void *MemCpy(void *__restrict__ dest, const void *__restrict__ src, Size len)
|
|
{
|
|
K_ASSERT(len >= 0);
|
|
|
|
#if defined(__clang__)
|
|
// LLVM guarantees sane behavior
|
|
__builtin_memcpy(dest, src, (size_t)len);
|
|
#else
|
|
if (len) {
|
|
memcpy(dest, src, (size_t)len);
|
|
}
|
|
#endif
|
|
return dest;
|
|
}
|
|
static inline void *MemMove(void *dest, const void *src, Size len)
|
|
{
|
|
K_ASSERT(len >= 0);
|
|
|
|
#if defined(__clang__)
|
|
// LLVM guarantees sane behavior
|
|
__builtin_memmove(dest, src, (size_t)len);
|
|
#else
|
|
if (len) {
|
|
memmove(dest, src, (size_t)len);
|
|
}
|
|
#endif
|
|
return dest;
|
|
}
|
|
static inline void *MemSet(void *dest, int c, Size len)
|
|
{
|
|
K_ASSERT(len >= 0);
|
|
|
|
#if defined(__clang__)
|
|
// LLVM guarantees sane behavior
|
|
__builtin_memset(dest, c, (size_t)len);
|
|
#else
|
|
if (len) {
|
|
memset(dest, c, (size_t)len);
|
|
}
|
|
#endif
|
|
return dest;
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
|
|
void *MemMem(const void *src, Size src_len, const void *needle, Size needle_len);
|
|
|
|
#else
|
|
|
|
static inline void *MemMem(const void *src, Size src_len, const void *needle, Size needle_len)
|
|
{
|
|
K_ASSERT(src_len >= 0);
|
|
K_ASSERT(needle_len > 0);
|
|
|
|
void *ptr = memmem(src, (size_t)src_len, needle, (size_t)needle_len);
|
|
return ptr;
|
|
}
|
|
|
|
#endif
|
|
|
|
// Implemented for translations, but we need it before we get to this part
|
|
const char *T(const char *key);
|
|
|
|
template <typename T, typename = typename std::enable_if<std::is_enum<T>::value, T>>
|
|
typename std::underlying_type<T>::type MaskEnum(T value)
|
|
{
|
|
auto mask = 1 << static_cast<typename std::underlying_type<T>::type>(value);
|
|
return (typename std::underlying_type<T>::type)mask;
|
|
}
|
|
|
|
template <typename Fun>
|
|
class DeferGuard {
|
|
K_DELETE_COPY(DeferGuard)
|
|
|
|
Fun f;
|
|
bool enabled;
|
|
|
|
public:
|
|
DeferGuard() = delete;
|
|
DeferGuard(Fun f_, bool enable = true) : f(std::move(f_)), enabled(enable) {}
|
|
~DeferGuard()
|
|
{
|
|
if (enabled) {
|
|
f();
|
|
}
|
|
}
|
|
|
|
DeferGuard(DeferGuard &&other)
|
|
: f(std::move(other.f)), enabled(other.enabled)
|
|
{
|
|
other.enabled = false;
|
|
}
|
|
|
|
void Disable() { enabled = false; }
|
|
};
|
|
|
|
// Honestly, I don't understand all the details in there, this comes from Andrei Alexandrescu.
|
|
// https://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C
|
|
struct DeferGuardHelper {};
|
|
template <typename Fun>
|
|
DeferGuard<Fun> operator+(DeferGuardHelper, Fun &&f)
|
|
{
|
|
return DeferGuard<Fun>(std::forward<Fun>(f));
|
|
}
|
|
|
|
// Write 'DEFER { code };' to do something at the end of the current scope, you
|
|
// can use DEFER_N(Name) if you need to disable the guard for some reason, and
|
|
// DEFER_NC(Name, Captures) if you need to capture values.
|
|
#define K_DEFER \
|
|
auto K_UNIQUE_NAME(defer) = K::DeferGuardHelper() + [&]()
|
|
#define K_DEFER_N(Name) \
|
|
auto Name = K::DeferGuardHelper() + [&]()
|
|
#define K_DEFER_C(...) \
|
|
auto K_UNIQUE_NAME(defer) = K::DeferGuardHelper() + [&, __VA_ARGS__]()
|
|
#define K_DEFER_NC(Name, ...) \
|
|
auto Name = K::DeferGuardHelper() + [&, __VA_ARGS__]()
|
|
|
|
template <typename T>
|
|
class NoDestroy {
|
|
K_DELETE_COPY(NoDestroy);
|
|
|
|
alignas(T) uint8_t data[K_SIZE(T)];
|
|
|
|
public:
|
|
template <class... Args>
|
|
NoDestroy(Args&&... args) { new (data) T(std::forward<Args>(args)...); }
|
|
|
|
~NoDestroy() = default;
|
|
|
|
const T *Get() const { return (const T *)(data); }
|
|
T *Get() { return (T*)data; }
|
|
|
|
const T &operator*() const { return *Get(); }
|
|
T& operator*() { return *Get(); }
|
|
const T *operator->() const { return Get(); }
|
|
T *operator->() { return Get(); }
|
|
};
|
|
|
|
// Heavily inspired from FunctionRef in LLVM
|
|
template<typename Fn> class FunctionRef;
|
|
template<typename Ret, typename ...Params>
|
|
class FunctionRef<Ret(Params...)> {
|
|
Ret (*callback)(intptr_t callable, Params ...params) = nullptr;
|
|
intptr_t callable;
|
|
|
|
template<typename Callable>
|
|
static Ret callback_fn(intptr_t callable, Params ...params)
|
|
{ return (*reinterpret_cast<Callable*>(callable))(std::forward<Params>(params)...); }
|
|
|
|
public:
|
|
FunctionRef() = default;
|
|
|
|
template <typename Callable>
|
|
FunctionRef(Callable &&callable,
|
|
std::enable_if_t<!std::is_same<std::remove_cv_t<std::remove_reference_t<Callable>>, FunctionRef>::value> * = nullptr,
|
|
std::enable_if_t<std::is_void<Ret>::value ||
|
|
std::is_convertible<decltype(std::declval<Callable>()(std::declval<Params>()...)),
|
|
Ret>::value> * = nullptr)
|
|
: callback(callback_fn<typename std::remove_reference<Callable>::type>),
|
|
callable(reinterpret_cast<intptr_t>(&callable)) {}
|
|
|
|
Ret operator()(Params ...params) const
|
|
{ return callback(callable, std::forward<Params>(params)...); }
|
|
|
|
bool IsValid() const { return callback; }
|
|
};
|
|
|
|
template <typename T>
|
|
T MultiCmp()
|
|
{
|
|
return 0;
|
|
}
|
|
template <typename T, typename... Args>
|
|
T MultiCmp(T cmp_value, Args... other_args)
|
|
{
|
|
if (cmp_value) {
|
|
return cmp_value;
|
|
} else {
|
|
return MultiCmp<T>(other_args...);
|
|
}
|
|
}
|
|
|
|
template <typename T, typename U>
|
|
T ApplyMask(T value, U mask, bool enable)
|
|
{
|
|
if (enable) {
|
|
return value | (T)mask;
|
|
} else {
|
|
return value & ~(T)mask;
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
struct Vec2 {
|
|
T x;
|
|
T y;
|
|
};
|
|
|
|
template <typename T>
|
|
struct Vec3 {
|
|
T x;
|
|
T y;
|
|
T z;
|
|
};
|
|
|
|
template <Size N>
|
|
class Bitset {
|
|
public:
|
|
template <typename T>
|
|
class Iterator {
|
|
public:
|
|
typedef std::input_iterator_tag iterator_category;
|
|
typedef Size value_type;
|
|
typedef Size difference_type;
|
|
typedef Iterator *pointer;
|
|
typedef Iterator &reference;
|
|
|
|
T *bitset = nullptr;
|
|
Size offset;
|
|
size_t bits = 0;
|
|
int ctz;
|
|
|
|
Iterator() = default;
|
|
Iterator(T *bitset, Size offset)
|
|
: bitset(bitset), offset(offset - 1)
|
|
{
|
|
operator++();
|
|
}
|
|
|
|
Size operator*() const
|
|
{
|
|
K_ASSERT(offset <= K_LEN(bitset->data));
|
|
|
|
if (offset == K_LEN(bitset->data))
|
|
return -1;
|
|
return offset * K_SIZE(size_t) * 8 + ctz;
|
|
}
|
|
|
|
Iterator &operator++()
|
|
{
|
|
K_ASSERT(offset <= K_LEN(bitset->data));
|
|
|
|
while (!bits) {
|
|
if (offset == K_LEN(bitset->data) - 1)
|
|
return *this;
|
|
bits = bitset->data[++offset];
|
|
}
|
|
|
|
ctz = CountTrailingZeros((uint64_t)bits);
|
|
bits ^= (size_t)1 << ctz;
|
|
|
|
return *this;
|
|
}
|
|
|
|
Iterator operator++(int)
|
|
{
|
|
Iterator ret = *this;
|
|
++(*this);
|
|
return ret;
|
|
}
|
|
|
|
bool operator==(const Iterator &other) const
|
|
{ return bitset == other.bitset && offset == other.offset; }
|
|
bool operator!=(const Iterator &other) const { return !(*this == other); }
|
|
};
|
|
|
|
typedef Size value_type;
|
|
typedef Iterator<Bitset> iterator_type;
|
|
|
|
static constexpr Size Bits = N;
|
|
size_t data[(N + K_BITS(size_t) - 1) / K_BITS(size_t)] = {};
|
|
|
|
constexpr Bitset() = default;
|
|
constexpr Bitset(std::initializer_list<Size> bits)
|
|
{
|
|
for (Size idx: bits) {
|
|
Size offset = idx / (K_SIZE(size_t) * 8);
|
|
size_t mask = (size_t)1 << (idx % (K_SIZE(size_t) * 8));
|
|
|
|
data[offset] |= mask;
|
|
}
|
|
}
|
|
|
|
void Clear()
|
|
{
|
|
MemSet(data, 0, K_SIZE(data));
|
|
}
|
|
|
|
Iterator<Bitset> begin() { return Iterator<Bitset>(this, 0); }
|
|
Iterator<const Bitset> begin() const { return Iterator<const Bitset>(this, 0); }
|
|
Iterator<Bitset> end() { return Iterator<Bitset>(this, K_LEN(data)); }
|
|
Iterator<const Bitset> end() const { return Iterator<const Bitset>(this, K_LEN(data)); }
|
|
|
|
Size PopCount() const
|
|
{
|
|
Size count = 0;
|
|
for (size_t bits: data) {
|
|
#if K_SIZE_MAX == INT64_MAX
|
|
count += K::PopCount((uint64_t)bits);
|
|
#else
|
|
count += K::PopCount((uint32_t)bits);
|
|
#endif
|
|
}
|
|
return count;
|
|
}
|
|
|
|
inline bool Test(Size idx) const
|
|
{
|
|
K_ASSERT(idx >= 0 && idx < N);
|
|
|
|
Size offset = idx / (K_SIZE(size_t) * 8);
|
|
size_t mask = (size_t)1 << (idx % (K_SIZE(size_t) * 8));
|
|
|
|
return data[offset] & mask;
|
|
}
|
|
inline void Set(Size idx, bool value = true)
|
|
{
|
|
K_ASSERT(idx >= 0 && idx < N);
|
|
|
|
Size offset = idx / (K_SIZE(size_t) * 8);
|
|
size_t mask = (size_t)1 << (idx % (K_SIZE(size_t) * 8));
|
|
|
|
data[offset] = ApplyMask(data[offset], mask, value);
|
|
}
|
|
inline bool TestAndSet(Size idx, bool value = true)
|
|
{
|
|
K_ASSERT(idx >= 0 && idx < N);
|
|
|
|
Size offset = idx / (K_SIZE(size_t) * 8);
|
|
size_t mask = (size_t)1 << (idx % (K_SIZE(size_t) * 8));
|
|
|
|
bool ret = data[offset] & mask;
|
|
data[offset] = ApplyMask(data[offset], mask, value);
|
|
|
|
return ret;
|
|
}
|
|
|
|
Bitset &operator&=(const Bitset &other)
|
|
{
|
|
for (Size i = 0; i < K_LEN(data); i++) {
|
|
data[i] &= other.data[i];
|
|
}
|
|
return *this;
|
|
}
|
|
Bitset operator&(const Bitset &other)
|
|
{
|
|
Bitset ret;
|
|
for (Size i = 0; i < K_LEN(data); i++) {
|
|
ret.data[i] = data[i] & other.data[i];
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Bitset &operator|=(const Bitset &other)
|
|
{
|
|
for (Size i = 0; i < K_LEN(data); i++) {
|
|
data[i] |= other.data[i];
|
|
}
|
|
return *this;
|
|
}
|
|
Bitset operator|(const Bitset &other)
|
|
{
|
|
Bitset ret;
|
|
for (Size i = 0; i < K_LEN(data); i++) {
|
|
ret.data[i] = data[i] | other.data[i];
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Bitset &operator^=(const Bitset &other)
|
|
{
|
|
for (Size i = 0; i < K_LEN(data); i++) {
|
|
data[i] ^= other.data[i];
|
|
}
|
|
return *this;
|
|
}
|
|
Bitset operator^(const Bitset &other)
|
|
{
|
|
Bitset ret;
|
|
for (Size i = 0; i < K_LEN(data); i++) {
|
|
ret.data[i] = data[i] ^ other.data[i];
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Bitset &Flip()
|
|
{
|
|
for (Size i = 0; i < K_LEN(data); i++) {
|
|
data[i] = ~data[i];
|
|
}
|
|
return *this;
|
|
}
|
|
Bitset operator~()
|
|
{
|
|
Bitset ret;
|
|
for (Size i = 0; i < K_LEN(data); i++) {
|
|
ret.data[i] = ~data[i];
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// XXX: Shift operators
|
|
};
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Memory / Allocator
|
|
// ------------------------------------------------------------------------
|
|
|
|
// I'd love to make Span default to { nullptr, 0 } but unfortunately that makes
|
|
// it a non-POD and prevents putting it in a union.
|
|
template <typename T>
|
|
struct Span {
|
|
T *ptr;
|
|
Size len;
|
|
|
|
Span() = default;
|
|
constexpr Span(T &value) : ptr(&value), len(1) {}
|
|
constexpr Span(std::initializer_list<T> l) : ptr(l.begin()), len((Size)l.size()) {}
|
|
constexpr Span(T *ptr_, Size len_) : ptr(ptr_), len(len_) {}
|
|
template <Size N>
|
|
constexpr Span(T (&arr)[N]) : ptr(arr), len(N) {}
|
|
|
|
constexpr void Reset()
|
|
{
|
|
ptr = nullptr;
|
|
len = 0;
|
|
}
|
|
|
|
constexpr T *begin() { return ptr; }
|
|
constexpr const T *begin() const { return ptr; }
|
|
constexpr T *end() { return ptr + len; }
|
|
constexpr const T *end() const { return ptr + len; }
|
|
|
|
constexpr bool IsValid() const { return ptr; }
|
|
|
|
constexpr T &operator[](Size idx)
|
|
{
|
|
K_ASSERT(idx >= 0 && idx < len);
|
|
return ptr[idx];
|
|
}
|
|
constexpr const T &operator[](Size idx) const
|
|
{
|
|
K_ASSERT(idx >= 0 && idx < len);
|
|
return ptr[idx];
|
|
}
|
|
|
|
constexpr operator Span<const T>() const { return Span<const T>(ptr, len); }
|
|
|
|
constexpr bool operator==(const Span &other) const
|
|
{
|
|
if (len != other.len)
|
|
return false;
|
|
|
|
for (Size i = 0; i < len; i++) {
|
|
if (ptr[i] != other.ptr[i])
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
constexpr bool operator!=(const Span &other) const { return !(*this == other); }
|
|
|
|
constexpr Span Take(Size offset, Size sub_len) const
|
|
{
|
|
K_ASSERT(sub_len >= 0 && sub_len <= len);
|
|
K_ASSERT(offset >= 0 && offset <= len - sub_len);
|
|
|
|
Span<T> sub = { ptr + offset, sub_len };
|
|
return sub;
|
|
}
|
|
|
|
template <typename U>
|
|
constexpr Span<U> As() const { return Span<U>((U *)ptr, len); }
|
|
};
|
|
|
|
// Use strlen() to build Span<const char> instead of the template-based
|
|
// array constructor.
|
|
template <>
|
|
struct Span<const char> {
|
|
const char *ptr;
|
|
Size len;
|
|
|
|
Span() = default;
|
|
constexpr Span(const char &ch) : ptr(&ch), len(1) {}
|
|
constexpr Span(const char *ptr_, Size len_) : ptr(ptr_), len(len_) {}
|
|
template <Size N>
|
|
Span(const char (&arr)[N]) : ptr(arr), len(strnlen(arr, N)) {}
|
|
#if defined(__clang__) || defined(_MSC_VER)
|
|
constexpr Span(const char *const &str) : ptr(str), len(str ? (Size)__builtin_strlen(str) : 0) {}
|
|
#else
|
|
constexpr Span(const char *const &str) : ptr(str), len(str ? (Size)strlen(str) : 0) {}
|
|
#endif
|
|
|
|
constexpr void Reset()
|
|
{
|
|
ptr = nullptr;
|
|
len = 0;
|
|
}
|
|
|
|
constexpr const char *begin() const { return ptr; }
|
|
constexpr const char *end() const { return ptr + len; }
|
|
|
|
constexpr bool IsValid() const { return ptr; }
|
|
|
|
constexpr char operator[](Size idx) const
|
|
{
|
|
K_ASSERT(idx >= 0 && idx < len);
|
|
return ptr[idx];
|
|
}
|
|
|
|
// The implementation comes later, after TestStr() is available
|
|
constexpr bool operator==(Span<const char> other) const;
|
|
constexpr bool operator==(const char *other) const;
|
|
constexpr bool operator!=(Span<const char> other) const { return !(*this == other); }
|
|
constexpr bool operator!=(const char *other) const { return !(*this == other); }
|
|
|
|
constexpr Span Take(Size offset, Size sub_len) const
|
|
{
|
|
K_ASSERT(sub_len >= 0 && sub_len <= len);
|
|
K_ASSERT(offset >= 0 && offset <= len - sub_len);
|
|
|
|
Span<const char> sub = { ptr + offset, sub_len };
|
|
return sub;
|
|
}
|
|
|
|
template <typename U>
|
|
constexpr Span<U> As() const { return Span<U>((U *)ptr, len); }
|
|
};
|
|
|
|
template <typename T>
|
|
static constexpr inline Span<T> MakeSpan(T *ptr, Size len)
|
|
{
|
|
return Span<T>(ptr, len);
|
|
}
|
|
template <typename T>
|
|
static constexpr inline Span<T> MakeSpan(T *ptr, T *end)
|
|
{
|
|
return Span<T>(ptr, end - ptr);
|
|
}
|
|
template <typename T, Size N>
|
|
static constexpr inline Span<T> MakeSpan(T (&arr)[N])
|
|
{
|
|
return Span<T>(arr, N);
|
|
}
|
|
|
|
template <typename T>
|
|
class Strider {
|
|
public:
|
|
void *ptr = nullptr;
|
|
Size stride;
|
|
|
|
Strider() = default;
|
|
constexpr Strider(T *ptr_) : ptr(ptr_), stride(K_SIZE(T)) {}
|
|
constexpr Strider(T *ptr_, Size stride_) : ptr(ptr_), stride(stride_) {}
|
|
|
|
constexpr bool IsValid() const { return ptr; }
|
|
|
|
constexpr T &operator[](Size idx) const
|
|
{
|
|
K_ASSERT(idx >= 0);
|
|
return *(T *)((uint8_t *)ptr + (idx * stride));
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
static constexpr inline Strider<T> MakeStrider(T *ptr)
|
|
{
|
|
return Strider<T>(ptr, K_SIZE(T));
|
|
}
|
|
template <typename T>
|
|
static constexpr inline Strider<T> MakeStrider(T *ptr, Size stride)
|
|
{
|
|
return Strider<T>(ptr, stride);
|
|
}
|
|
template <typename T, Size N>
|
|
static constexpr inline Strider<T> MakeStrider(T (&arr)[N])
|
|
{
|
|
return Strider<T>(arr, K_SIZE(T));
|
|
}
|
|
|
|
enum class AllocFlag {
|
|
Zero = 1,
|
|
Resizable = 2
|
|
};
|
|
|
|
class Allocator {
|
|
K_DELETE_COPY(Allocator)
|
|
|
|
public:
|
|
Allocator() = default;
|
|
virtual ~Allocator() = default;
|
|
|
|
virtual void *Allocate(Size size, unsigned int flags = 0) = 0;
|
|
virtual void *Resize(void *ptr, Size old_size, Size new_size, unsigned int flags = 0) = 0;
|
|
virtual void Release(const void *ptr, Size size) = 0;
|
|
};
|
|
|
|
Allocator *GetDefaultAllocator();
|
|
Allocator *GetNullAllocator();
|
|
|
|
static inline void *AllocateRaw(Allocator *alloc, Size size, unsigned int flags = 0)
|
|
{
|
|
K_ASSERT(size >= 0);
|
|
|
|
if (!alloc) {
|
|
alloc = GetDefaultAllocator();
|
|
}
|
|
|
|
void *ptr = alloc->Allocate(size, flags);
|
|
return ptr;
|
|
}
|
|
|
|
template <typename T>
|
|
T *AllocateOne(Allocator *alloc, unsigned int flags = 0)
|
|
{
|
|
if (!alloc) {
|
|
alloc = GetDefaultAllocator();
|
|
}
|
|
|
|
Size size = K_SIZE(T);
|
|
|
|
T *ptr = (T *)alloc->Allocate(size, flags);
|
|
return ptr;
|
|
}
|
|
|
|
template <typename T>
|
|
Span<T> AllocateSpan(Allocator *alloc, Size len, unsigned int flags = 0)
|
|
{
|
|
K_ASSERT(len >= 0);
|
|
|
|
if (!alloc) {
|
|
alloc = GetDefaultAllocator();
|
|
}
|
|
|
|
Size size = len * K_SIZE(T);
|
|
|
|
T *ptr = (T *)alloc->Allocate(size, flags);
|
|
return MakeSpan(ptr, len);
|
|
}
|
|
|
|
static inline void *ResizeRaw(Allocator *alloc, void *ptr, Size old_size, Size new_size,
|
|
unsigned int flags = 0)
|
|
{
|
|
K_ASSERT(new_size >= 0);
|
|
|
|
if (!alloc) {
|
|
alloc = GetDefaultAllocator();
|
|
}
|
|
|
|
ptr = alloc->Resize(ptr, old_size, new_size, flags);
|
|
return ptr;
|
|
}
|
|
|
|
template <typename T>
|
|
Span<T> ResizeSpan(Allocator *alloc, Span<T> mem, Size new_len,
|
|
unsigned int flags = 0)
|
|
{
|
|
K_ASSERT(new_len >= 0);
|
|
|
|
if (!alloc) {
|
|
alloc = GetDefaultAllocator();
|
|
}
|
|
|
|
Size old_size = mem.len * K_SIZE(T);
|
|
Size new_size = new_len * K_SIZE(T);
|
|
|
|
mem.ptr = (T *)alloc->Resize(mem.ptr, old_size, new_size, flags);
|
|
return MakeSpan(mem.ptr, new_len);
|
|
}
|
|
|
|
static inline void ReleaseRaw(Allocator *alloc, const void *ptr, Size size)
|
|
{
|
|
if (!alloc) {
|
|
alloc = GetDefaultAllocator();
|
|
}
|
|
|
|
alloc->Release(ptr, size);
|
|
}
|
|
|
|
template<typename T>
|
|
void ReleaseOne(Allocator *alloc, T *ptr)
|
|
{
|
|
if (!alloc) {
|
|
alloc = GetDefaultAllocator();
|
|
}
|
|
|
|
alloc->Release((void *)ptr, K_SIZE(T));
|
|
}
|
|
|
|
template<typename T>
|
|
void ReleaseSpan(Allocator *alloc, Span<T> mem)
|
|
{
|
|
if (!alloc) {
|
|
alloc = GetDefaultAllocator();
|
|
}
|
|
|
|
Size size = mem.len * K_SIZE(T);
|
|
|
|
alloc->Release((void *)mem.ptr, size);
|
|
}
|
|
|
|
class LinkedAllocator final: public Allocator {
|
|
struct Bucket {
|
|
Bucket *prev;
|
|
Bucket *next;
|
|
uint8_t data[];
|
|
};
|
|
|
|
Allocator *allocator;
|
|
|
|
Bucket *list = nullptr;
|
|
|
|
public:
|
|
LinkedAllocator(Allocator *alloc = nullptr) : allocator(alloc) {}
|
|
~LinkedAllocator() override { ReleaseAll(); }
|
|
|
|
LinkedAllocator(LinkedAllocator &&other) { *this = std::move(other); }
|
|
LinkedAllocator& operator=(LinkedAllocator &&other);
|
|
|
|
void ReleaseAll();
|
|
void ReleaseAllExcept(void *ptr);
|
|
|
|
void *Allocate(Size size, unsigned int flags = 0) override;
|
|
void *Resize(void *ptr, Size old_size, Size new_size, unsigned int flags = 0) override;
|
|
void Release(const void *ptr, Size size) override;
|
|
|
|
bool IsUsed() const { return list; }
|
|
|
|
void GiveTo(LinkedAllocator *alloc);
|
|
|
|
private:
|
|
static Bucket *PointerToBucket(void *ptr);
|
|
};
|
|
|
|
class BlockAllocator: public Allocator {
|
|
struct Bucket {
|
|
Size used;
|
|
uint8_t data[];
|
|
};
|
|
|
|
LinkedAllocator allocator;
|
|
Size block_size;
|
|
|
|
Bucket *current_bucket = nullptr;
|
|
uint8_t *last_alloc = nullptr;
|
|
|
|
public:
|
|
BlockAllocator(Size block_size = K_BLOCK_ALLOCATOR_DEFAULT_SIZE)
|
|
: block_size(block_size)
|
|
{
|
|
K_ASSERT(block_size > 0);
|
|
}
|
|
|
|
BlockAllocator(BlockAllocator &&other) { *this = std::move(other); }
|
|
BlockAllocator& operator=(BlockAllocator &&other);
|
|
|
|
void Reset();
|
|
void ReleaseAll();
|
|
|
|
void *Allocate(Size size, unsigned int flags = 0) override;
|
|
void *Resize(void *ptr, Size old_size, Size new_size, unsigned int flags = 0) override;
|
|
void Release(const void *ptr, Size size) override;
|
|
|
|
bool IsUsed() const { return allocator.IsUsed(); }
|
|
|
|
void GiveTo(LinkedAllocator *alloc);
|
|
void GiveTo(BlockAllocator *alloc) { GiveTo(&alloc->allocator); }
|
|
|
|
private:
|
|
bool AllocateSeparately(Size aligned_size) const { return aligned_size > block_size / 2; }
|
|
};
|
|
|
|
void *AllocateSafe(Size len);
|
|
void ReleaseSafe(void *ptr, Size len);
|
|
void ZeroSafe(void *ptr, Size len);
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Reference counting
|
|
// ------------------------------------------------------------------------
|
|
|
|
template <typename T>
|
|
class RetainPtr {
|
|
T *p = nullptr;
|
|
|
|
public:
|
|
RetainPtr() = default;
|
|
RetainPtr(T *p, void (*delete_func)(std::remove_const_t<T> *))
|
|
: p(p)
|
|
{
|
|
K_ASSERT(p);
|
|
K_ASSERT(delete_func);
|
|
K_ASSERT(!p->delete_func || delete_func == p->delete_func);
|
|
|
|
p->Ref();
|
|
p->delete_func = delete_func;
|
|
}
|
|
RetainPtr(T *p, bool ref = true)
|
|
: p(p)
|
|
{
|
|
if (p) {
|
|
K_ASSERT(p->delete_func);
|
|
|
|
if (ref) {
|
|
p->Ref();
|
|
}
|
|
}
|
|
}
|
|
|
|
~RetainPtr()
|
|
{
|
|
if (p && !p->Unref()) {
|
|
p->delete_func((std::remove_const_t<T> *)p);
|
|
}
|
|
}
|
|
|
|
RetainPtr(const RetainPtr &other)
|
|
{
|
|
p = other.p;
|
|
if (p) {
|
|
p->Ref();
|
|
}
|
|
}
|
|
RetainPtr &operator=(const RetainPtr &other)
|
|
{
|
|
if (p && !p->Unref()) {
|
|
p->delete_func((std::remove_const_t<T> *)p);
|
|
}
|
|
|
|
p = other.p;
|
|
if (p) {
|
|
p->Ref();
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
operator RetainPtr<const T>() const
|
|
{
|
|
RetainPtr<const T> ptr((const T *)p);
|
|
return ptr;
|
|
}
|
|
|
|
bool IsValid() const { return p; }
|
|
operator bool() const { return p; }
|
|
|
|
T &operator*() const
|
|
{
|
|
K_ASSERT(p);
|
|
return *p;
|
|
}
|
|
T *operator->() const { return p; }
|
|
T *GetRaw() const { return p; }
|
|
};
|
|
|
|
template <typename T>
|
|
class RetainObject {
|
|
mutable void (*delete_func)(T *) = nullptr;
|
|
mutable std::atomic_int refcount { 0 };
|
|
|
|
public:
|
|
void Ref() const { refcount++; }
|
|
bool Unref() const
|
|
{
|
|
int new_count = --refcount;
|
|
K_ASSERT(new_count >= 0);
|
|
return new_count;
|
|
}
|
|
|
|
friend class RetainPtr<T>;
|
|
friend class RetainPtr<const T>;
|
|
};
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Strings
|
|
// ------------------------------------------------------------------------
|
|
|
|
bool CopyString(const char *str, Span<char> buf);
|
|
bool CopyString(Span<const char> str, Span<char> buf);
|
|
Span<char> DuplicateString(Span<const char> str, Allocator *alloc);
|
|
|
|
static constexpr inline bool IsAsciiAlpha(int c)
|
|
{
|
|
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
|
|
}
|
|
static constexpr inline bool IsAsciiDigit(int c)
|
|
{
|
|
return (c >= '0' && c <= '9');
|
|
}
|
|
static constexpr inline bool IsAsciiAlphaOrDigit(int c)
|
|
{
|
|
return IsAsciiAlpha(c) || IsAsciiDigit(c);
|
|
}
|
|
static constexpr inline bool IsAsciiWhite(int c)
|
|
{
|
|
return c == ' ' || c == '\t' || c == '\v' ||
|
|
c == '\n' || c == '\r' || c == '\f';
|
|
}
|
|
static constexpr inline bool IsAsciiControl(int c)
|
|
{
|
|
return c == 0x7F || ((uint8_t)c < ' ' && c != '\t');
|
|
}
|
|
|
|
static constexpr inline char UpperAscii(int c)
|
|
{
|
|
if (c >= 'a' && c <= 'z') {
|
|
return (char)(c - 32);
|
|
} else {
|
|
return (char)c;
|
|
}
|
|
}
|
|
static constexpr inline char LowerAscii(int c)
|
|
{
|
|
if (c >= 'A' && c <= 'Z') {
|
|
return (char)(c + 32);
|
|
} else {
|
|
return (char)c;
|
|
}
|
|
}
|
|
|
|
static constexpr inline bool TestStr(Span<const char> str1, Span<const char> str2)
|
|
{
|
|
if (str1.len != str2.len)
|
|
return false;
|
|
for (Size i = 0; i < str1.len; i++) {
|
|
if (str1[i] != str2[i])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
static constexpr inline bool TestStr(Span<const char> str1, const char *str2)
|
|
{
|
|
Size i = 0;
|
|
for (; i < str1.len && str2[i]; i++) {
|
|
if (str1[i] != str2[i])
|
|
return false;
|
|
}
|
|
return (i == str1.len) && !str2[i];
|
|
}
|
|
static constexpr inline bool TestStr(const char *str1, Span<const char> str2)
|
|
{ return TestStr(str2, str1); }
|
|
static constexpr inline bool TestStr(const char *str1, const char *str2)
|
|
{
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
return !__builtin_strcmp(str1, str2);
|
|
#else
|
|
Size i = 0;
|
|
for (; str1[i]; i++) {
|
|
if (str2[i] != str1[i])
|
|
return false;
|
|
}
|
|
return !str2[i];
|
|
#endif
|
|
}
|
|
|
|
// Allow direct Span<const char> equality comparison
|
|
constexpr inline bool Span<const char>::operator==(Span<const char> other) const
|
|
{ return TestStr(*this, other); }
|
|
constexpr inline bool Span<const char>::operator==(const char *other) const
|
|
{ return TestStr(*this, other); }
|
|
|
|
// Case insensitive (ASCII) versions
|
|
static constexpr inline bool TestStrI(Span<const char> str1, Span<const char> str2)
|
|
{
|
|
if (str1.len != str2.len)
|
|
return false;
|
|
for (Size i = 0; i < str1.len; i++) {
|
|
if (LowerAscii(str1[i]) != LowerAscii(str2[i]))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
static constexpr inline bool TestStrI(Span<const char> str1, const char *str2)
|
|
{
|
|
Size i = 0;
|
|
for (; i < str1.len && str2[i]; i++) {
|
|
if (LowerAscii(str1[i]) != LowerAscii(str2[i]))
|
|
return false;
|
|
}
|
|
return (i == str1.len) && !str2[i];
|
|
}
|
|
static constexpr inline bool TestStrI(const char *str1, Span<const char> str2)
|
|
{ return TestStrI(str2, str1); }
|
|
static constexpr inline bool TestStrI(const char *str1, const char *str2)
|
|
{
|
|
Size i = 0;
|
|
int delta = 0;
|
|
do {
|
|
delta = LowerAscii(str1[i]) - LowerAscii(str2[i]);
|
|
} while (str1[i++] && !delta);
|
|
return !delta;
|
|
}
|
|
|
|
static constexpr inline int CmpStr(Span<const char> str1, Span<const char> str2)
|
|
{
|
|
for (Size i = 0; i < str1.len && i < str2.len; i++) {
|
|
int delta = str1[i] - str2[i];
|
|
if (delta)
|
|
return delta;
|
|
}
|
|
if (str1.len < str2.len) {
|
|
return -str2[str1.len];
|
|
} else if (str1.len > str2.len) {
|
|
return str1[str2.len];
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
static constexpr inline int CmpStr(Span<const char> str1, const char *str2)
|
|
{
|
|
Size i = 0;
|
|
for (; i < str1.len && str2[i]; i++) {
|
|
int delta = str1[i] - str2[i];
|
|
if (delta)
|
|
return delta;
|
|
}
|
|
if (str1.len == i) {
|
|
return -str2[i];
|
|
} else {
|
|
return str1[i];
|
|
}
|
|
}
|
|
static constexpr inline int CmpStr(const char *str1, Span<const char> str2)
|
|
{ return -CmpStr(str2, str1); }
|
|
static constexpr inline int CmpStr(const char *str1, const char *str2)
|
|
{
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
return __builtin_strcmp(str1, str2);
|
|
#else
|
|
Size i = 0;
|
|
for (; str1[i]; i++) {
|
|
int delta = str1[i] - str2[i];
|
|
if (delta)
|
|
return delta;
|
|
}
|
|
return str2[i] - str1[i];
|
|
#endif
|
|
}
|
|
|
|
static inline bool StartsWith(Span<const char> str, Span<const char> prefix)
|
|
{
|
|
Size i = 0;
|
|
while (i < str.len && i < prefix.len) {
|
|
if (str[i] != prefix[i])
|
|
return false;
|
|
i++;
|
|
}
|
|
return (i == prefix.len);
|
|
}
|
|
static inline bool StartsWith(Span<const char> str, const char *prefix)
|
|
{
|
|
Size i = 0;
|
|
while (i < str.len && prefix[i]) {
|
|
if (str[i] != prefix[i])
|
|
return false;
|
|
i++;
|
|
}
|
|
return !prefix[i];
|
|
}
|
|
static inline bool StartsWith(const char *str, Span<const char> prefix)
|
|
{
|
|
Size i = 0;
|
|
while (str[i] && i < prefix.len) {
|
|
if (str[i] != prefix[i])
|
|
return false;
|
|
i++;
|
|
}
|
|
return (i == prefix.len);
|
|
}
|
|
static inline bool StartsWith(const char *str, const char *prefix)
|
|
{
|
|
Size i = 0;
|
|
while (str[i] && prefix[i]) {
|
|
if (str[i] != prefix[i])
|
|
return false;
|
|
i++;
|
|
}
|
|
return !prefix[i];
|
|
}
|
|
|
|
static inline bool StartsWithI(Span<const char> str, Span<const char> prefix)
|
|
{
|
|
Size i = 0;
|
|
while (i < str.len && i < prefix.len) {
|
|
if (LowerAscii(str[i]) != LowerAscii(prefix[i]))
|
|
return false;
|
|
i++;
|
|
}
|
|
return (i == prefix.len);
|
|
}
|
|
static inline bool StartsWithI(Span<const char> str, const char *prefix)
|
|
{
|
|
Size i = 0;
|
|
while (i < str.len && prefix[i]) {
|
|
if (LowerAscii(str[i]) != LowerAscii(prefix[i]))
|
|
return false;
|
|
i++;
|
|
}
|
|
return !prefix[i];
|
|
}
|
|
static inline bool StartsWithI(const char *str, Span<const char> prefix)
|
|
{
|
|
Size i = 0;
|
|
while (str[i] && i < prefix.len) {
|
|
if (LowerAscii(str[i]) != LowerAscii(prefix[i]))
|
|
return false;
|
|
i++;
|
|
}
|
|
return (i == prefix.len);
|
|
}
|
|
static inline bool StartsWithI(const char *str, const char *prefix)
|
|
{
|
|
Size i = 0;
|
|
while (str[i] && prefix[i]) {
|
|
if (LowerAscii(str[i]) != LowerAscii(prefix[i]))
|
|
return false;
|
|
i++;
|
|
}
|
|
return !prefix[i];
|
|
}
|
|
|
|
static inline bool EndsWith(Span<const char> str, Span<const char> suffix)
|
|
{
|
|
Size i = str.len - 1;
|
|
Size j = suffix.len - 1;
|
|
|
|
while (i >= 0 && j >= 0) {
|
|
if (str[i] != suffix[j])
|
|
return false;
|
|
|
|
i--;
|
|
j--;
|
|
}
|
|
|
|
return j < 0;
|
|
}
|
|
static inline bool EndsWithI(Span<const char> str, Span<const char> suffix)
|
|
{
|
|
Size i = str.len - 1;
|
|
Size j = suffix.len - 1;
|
|
|
|
while (i >= 0 && j >= 0) {
|
|
if (LowerAscii(str[i]) != LowerAscii(suffix[j]))
|
|
return false;
|
|
|
|
i--;
|
|
j--;
|
|
}
|
|
|
|
return j < 0;
|
|
}
|
|
|
|
static inline Size FindStr(Span<const char> str, Span<const char> needle)
|
|
{
|
|
if (!needle.len)
|
|
return 0;
|
|
if (needle.len > str.len)
|
|
return -1;
|
|
|
|
Size end = str.len - needle.len;
|
|
|
|
for (Size i = 0; i <= end; i++) {
|
|
if (!memcmp(str.ptr + i, needle.ptr, (size_t)needle.len))
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
static inline Size FindStr(const char *str, const char *needle)
|
|
{
|
|
const char *ret = strstr(str, needle);
|
|
return ret ? ret - str : -1;
|
|
}
|
|
|
|
static inline Span<char> SplitStr(Span<char> str, char split_char, Span<char> *out_remainder = nullptr)
|
|
{
|
|
Size part_len = 0;
|
|
while (part_len < str.len) {
|
|
if (str[part_len] == split_char) {
|
|
if (out_remainder) {
|
|
*out_remainder = str.Take(part_len + 1, str.len - part_len - 1);
|
|
}
|
|
return str.Take(0, part_len);
|
|
}
|
|
part_len++;
|
|
}
|
|
|
|
if (out_remainder) {
|
|
*out_remainder = str.Take(str.len, 0);
|
|
}
|
|
return str;
|
|
}
|
|
static inline Span<char> SplitStr(char *str, char split_char, char **out_remainder = nullptr)
|
|
{
|
|
Size part_len = 0;
|
|
while (str[part_len]) {
|
|
if (str[part_len] == split_char) {
|
|
if (out_remainder) {
|
|
*out_remainder = str + part_len + 1;
|
|
}
|
|
return MakeSpan(str, part_len);
|
|
}
|
|
part_len++;
|
|
}
|
|
|
|
if (out_remainder) {
|
|
*out_remainder = str + part_len;
|
|
}
|
|
return MakeSpan(str, part_len);
|
|
}
|
|
static inline Span<const char> SplitStr(Span<const char> str, char split_char, Span<const char> *out_remainder = nullptr)
|
|
{ return SplitStr(MakeSpan((char *)str.ptr, str.len), split_char, (Span<char> *)out_remainder); }
|
|
static inline Span<const char> SplitStr(const char *str, char split_char, const char **out_remainder = nullptr)
|
|
{ return SplitStr((char *)str, split_char, (char **)out_remainder); }
|
|
|
|
static inline Span<char> SplitStr(Span<char> str, Span<const char> split, Span<char> *out_remainder = nullptr)
|
|
{
|
|
K_ASSERT(split.len);
|
|
|
|
Size part_len = 0;
|
|
while (part_len < str.len) {
|
|
if (StartsWith(str.Take(part_len, str.len - part_len), split)) {
|
|
if (out_remainder) {
|
|
*out_remainder = str.Take(part_len + split.len, str.len - part_len - split.len);
|
|
}
|
|
return str.Take(0, part_len);
|
|
}
|
|
part_len++;
|
|
}
|
|
|
|
if (out_remainder) {
|
|
*out_remainder = str.Take(str.len, 0);
|
|
}
|
|
return str;
|
|
}
|
|
static inline Span<char> SplitStr(char *str, Span<const char> split, char **out_remainder = nullptr)
|
|
{
|
|
K_ASSERT(split.len);
|
|
|
|
Size part_len = 0;
|
|
while (str[part_len]) {
|
|
if (StartsWith(str + part_len, split)) {
|
|
if (out_remainder) {
|
|
*out_remainder = str + part_len + split.len;
|
|
}
|
|
return MakeSpan(str, part_len);
|
|
}
|
|
part_len++;
|
|
}
|
|
|
|
if (out_remainder) {
|
|
*out_remainder = str + part_len;
|
|
}
|
|
return MakeSpan(str, part_len);
|
|
}
|
|
static inline Span<const char> SplitStr(Span<const char> str, Span<const char> split, Span<const char> *out_remainder = nullptr)
|
|
{ return SplitStr(MakeSpan((char *)str.ptr, str.len), split, (Span<char> *)out_remainder); }
|
|
static inline Span<const char> SplitStr(const char *str, Span<const char> split, const char **out_remainder = nullptr)
|
|
{ return SplitStr((char *)str, split, (char **)out_remainder); }
|
|
|
|
static inline Span<char> SplitStrLine(Span<char> str, Span<char> *out_remainder = nullptr)
|
|
{
|
|
Span<char> part = SplitStr(str, '\n', out_remainder);
|
|
if (part.len < str.len && part.len && part[part.len - 1] == '\r') {
|
|
part.len--;
|
|
}
|
|
return part;
|
|
}
|
|
static inline Span<char> SplitStrLine(char *str, char **out_remainder = nullptr)
|
|
{
|
|
Span<char> part = SplitStr(str, '\n', out_remainder);
|
|
if (str[part.len] && part.len && part[part.len - 1] == '\r') {
|
|
part.len--;
|
|
}
|
|
return part;
|
|
}
|
|
static inline Span<const char> SplitStrLine(Span<const char> str, Span<const char> *out_remainder = nullptr)
|
|
{ return SplitStrLine(MakeSpan((char *)str.ptr, str.len), (Span<char> *)out_remainder); }
|
|
static inline Span<const char> SplitStrLine(const char *str, const char **out_remainder = nullptr)
|
|
{ return SplitStrLine((char *)str, (char **)out_remainder); }
|
|
|
|
static inline Span<char> SplitStrAny(Span<char> str, const char *split_chars, Span<char> *out_remainder = nullptr)
|
|
{
|
|
Bitset<256> split_mask;
|
|
for (Size i = 0; split_chars[i]; i++) {
|
|
uint8_t c = (uint8_t)split_chars[i];
|
|
split_mask.Set(c);
|
|
}
|
|
|
|
Size part_len = 0;
|
|
while (part_len < str.len) {
|
|
uint8_t c = (uint8_t)str[part_len];
|
|
|
|
if (split_mask.Test(c)) {
|
|
if (out_remainder) {
|
|
*out_remainder = str.Take(part_len + 1, str.len - part_len - 1);
|
|
}
|
|
return str.Take(0, part_len);
|
|
}
|
|
part_len++;
|
|
}
|
|
|
|
if (out_remainder) {
|
|
*out_remainder = str.Take(str.len, 0);
|
|
}
|
|
return str;
|
|
}
|
|
static inline Span<char> SplitStrAny(char *str, const char *split_chars, char **out_remainder = nullptr)
|
|
{
|
|
Bitset<256> split_mask;
|
|
for (Size i = 0; split_chars[i]; i++) {
|
|
uint8_t c = (uint8_t)split_chars[i];
|
|
split_mask.Set(c);
|
|
}
|
|
|
|
Size part_len = 0;
|
|
while (str[part_len]) {
|
|
uint8_t c = (uint8_t)str[part_len];
|
|
|
|
if (split_mask.Test(c)) {
|
|
if (out_remainder) {
|
|
*out_remainder = str + part_len + 1;
|
|
}
|
|
return MakeSpan(str, part_len);
|
|
}
|
|
part_len++;
|
|
}
|
|
|
|
if (out_remainder) {
|
|
*out_remainder = str + part_len;
|
|
}
|
|
return MakeSpan(str, part_len);
|
|
}
|
|
static inline Span<const char> SplitStrAny(Span<const char> str, const char *split_chars, Span<const char> *out_remainder = nullptr)
|
|
{ return SplitStrAny(MakeSpan((char *)str.ptr, str.len), split_chars, (Span<char> *)out_remainder); }
|
|
static inline Span<const char> SplitStrAny(const char *str, const char *split_chars, const char **out_remainder = nullptr)
|
|
{ return SplitStrAny((char *)str, split_chars, (char **)out_remainder); }
|
|
|
|
static inline Span<const char> SplitStrReverse(Span<const char> str, char split_char,
|
|
Span<const char> *out_remainder = nullptr)
|
|
{
|
|
Size remainder_len = str.len - 1;
|
|
while (remainder_len >= 0) {
|
|
if (str[remainder_len] == split_char) {
|
|
if (out_remainder) {
|
|
*out_remainder = str.Take(0, remainder_len);
|
|
}
|
|
return str.Take(remainder_len + 1, str.len - remainder_len - 1);
|
|
}
|
|
remainder_len--;
|
|
}
|
|
|
|
if (out_remainder) {
|
|
*out_remainder = str.Take(0, 0);
|
|
}
|
|
return str;
|
|
}
|
|
static inline Span<const char> SplitStrReverse(const char *str, char split_char,
|
|
Span<const char> *out_remainder = nullptr)
|
|
{ return SplitStrReverse(MakeSpan(str, strlen(str)), split_char, out_remainder); }
|
|
|
|
static inline Span<const char> SplitStrReverseAny(Span<const char> str, const char *split_chars,
|
|
Span<const char> *out_remainder = nullptr)
|
|
{
|
|
Bitset<256> split_mask;
|
|
for (Size i = 0; split_chars[i]; i++) {
|
|
uint8_t c = (uint8_t)split_chars[i];
|
|
split_mask.Set(c);
|
|
}
|
|
|
|
Size remainder_len = str.len - 1;
|
|
while (remainder_len >= 0) {
|
|
uint8_t c = (uint8_t)str[remainder_len];
|
|
|
|
if (split_mask.Test(c)) {
|
|
if (out_remainder) {
|
|
*out_remainder = str.Take(0, remainder_len);
|
|
}
|
|
return str.Take(remainder_len + 1, str.len - remainder_len - 1);
|
|
}
|
|
remainder_len--;
|
|
}
|
|
|
|
if (out_remainder) {
|
|
*out_remainder = str.Take(0, 0);
|
|
}
|
|
return str;
|
|
}
|
|
static inline Span<const char> SplitStrReverseAny(const char *str, const char *split_chars,
|
|
Span<const char> *out_remainder = nullptr)
|
|
{ return SplitStrReverseAny(MakeSpan(str, strlen(str)), split_chars, out_remainder); }
|
|
|
|
static inline Span<char> TrimStrLeft(Span<char> str, char trim_char)
|
|
{
|
|
while (str.len && str[0] == trim_char && str[0]) {
|
|
str.ptr++;
|
|
str.len--;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
static inline Span<char> TrimStrRight(Span<char> str, char trim_char)
|
|
{
|
|
while (str.len && str[str.len - 1] == trim_char && str[str.len - 1]) {
|
|
str.len--;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
static inline Span<char> TrimStr(Span<char> str, char trim_char)
|
|
{
|
|
str = TrimStrRight(str, trim_char);
|
|
str = TrimStrLeft(str, trim_char);
|
|
|
|
return str;
|
|
}
|
|
static inline Span<const char> TrimStrLeft(Span<const char> str, char trim_char)
|
|
{ return TrimStrLeft(MakeSpan((char *)str.ptr, str.len), trim_char); }
|
|
static inline Span<const char> TrimStrRight(Span<const char> str, char trim_char)
|
|
{ return TrimStrRight(MakeSpan((char *)str.ptr, str.len), trim_char); }
|
|
static inline Span<const char> TrimStr(Span<const char> str,char trim_char)
|
|
{ return TrimStr(MakeSpan((char *)str.ptr, str.len), trim_char); }
|
|
|
|
static inline Span<char> TrimStrLeft(Span<char> str, const char *trim_chars = " \t\r\n")
|
|
{
|
|
while (str.len && strchr(trim_chars, str[0]) && str[0]) {
|
|
str.ptr++;
|
|
str.len--;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
static inline Span<char> TrimStrRight(Span<char> str, const char *trim_chars = " \t\r\n")
|
|
{
|
|
while (str.len && strchr(trim_chars, str[str.len - 1]) && str[str.len - 1]) {
|
|
str.len--;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
static inline Span<char> TrimStr(Span<char> str, const char *trim_chars = " \t\r\n")
|
|
{
|
|
str = TrimStrRight(str, trim_chars);
|
|
str = TrimStrLeft(str, trim_chars);
|
|
|
|
return str;
|
|
}
|
|
static inline Span<const char> TrimStrLeft(Span<const char> str, const char *trim_chars = " \t\r\n")
|
|
{ return TrimStrLeft(MakeSpan((char *)str.ptr, str.len), trim_chars); }
|
|
static inline Span<const char> TrimStrRight(Span<const char> str, const char *trim_chars = " \t\r\n")
|
|
{ return TrimStrRight(MakeSpan((char *)str.ptr, str.len), trim_chars); }
|
|
static inline Span<const char> TrimStr(Span<const char> str, const char *trim_chars = " \t\r\n")
|
|
{ return TrimStr(MakeSpan((char *)str.ptr, str.len), trim_chars); }
|
|
|
|
int CmpNatural(Span<const char> str1, Span<const char> str2);
|
|
int CmpNaturalI(Span<const char> str1, Span<const char> str2);
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Collections
|
|
// ------------------------------------------------------------------------
|
|
|
|
template <typename T, Size N, Size AlignAs = alignof(T)>
|
|
class LocalArray {
|
|
public:
|
|
alignas(AlignAs) T data[N];
|
|
Size len = 0;
|
|
|
|
typedef T value_type;
|
|
typedef T *iterator_type;
|
|
|
|
constexpr LocalArray() = default;
|
|
constexpr LocalArray(std::initializer_list<T> l)
|
|
{
|
|
K_ASSERT(l.size() <= N);
|
|
for (const T &it: l) {
|
|
data[len++] = it;
|
|
}
|
|
len = (Size)l.size();
|
|
}
|
|
|
|
void Clear()
|
|
{
|
|
for (Size i = 0; i < len; i++) {
|
|
data[i] = T();
|
|
}
|
|
len = 0;
|
|
}
|
|
|
|
operator Span<T>() { return Span<T>(data, len); }
|
|
operator Span<const T>() const { return Span<const T>(data, len); }
|
|
|
|
T *begin() { return data; }
|
|
const T *begin() const { return data; }
|
|
T *end() { return data + len; }
|
|
const T *end() const { return data + len; }
|
|
|
|
Size Available() const { return K_LEN(data) - len; }
|
|
|
|
T &operator[](Size idx)
|
|
{
|
|
K_ASSERT(idx >= 0 && idx < len);
|
|
return data[idx];
|
|
}
|
|
const T &operator[](Size idx) const
|
|
{
|
|
K_ASSERT(idx >= 0 && idx < len);
|
|
return data[idx];
|
|
}
|
|
|
|
bool operator==(const LocalArray &other) const
|
|
{
|
|
if (len != other.len)
|
|
return false;
|
|
|
|
for (Size i = 0; i < len; i++) {
|
|
if (data[i] != other.data[i])
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
bool operator!=(const LocalArray &other) const { return !(*this == other); }
|
|
|
|
T *AppendDefault(Size count = 1)
|
|
{
|
|
K_ASSERT(len <= N - count);
|
|
|
|
T *first = data + len;
|
|
if constexpr(!std::is_trivial<T>::value) {
|
|
for (Size i = 0; i < count; i++) {
|
|
new (data + len) T();
|
|
len++;
|
|
}
|
|
} else {
|
|
MemSet(first, 0, count * K_SIZE(T));
|
|
len += count;
|
|
}
|
|
|
|
return first;
|
|
}
|
|
|
|
T *Append(const T &value)
|
|
{
|
|
K_ASSERT(len < N);
|
|
|
|
T *it = data + len;
|
|
*it = value;
|
|
len++;
|
|
|
|
return it;
|
|
}
|
|
T *Append(T &&value)
|
|
{
|
|
K_ASSERT(len < N);
|
|
|
|
T *it = data + len;
|
|
*it = std::move(value);
|
|
len++;
|
|
|
|
return it;
|
|
}
|
|
T *Append(Span<const T> values)
|
|
{
|
|
K_ASSERT(values.len <= N - len);
|
|
|
|
T *it = data + len;
|
|
for (Size i = 0; i < values.len; i++) {
|
|
data[len + i] = values[i];
|
|
}
|
|
len += values.len;
|
|
|
|
return it;
|
|
}
|
|
|
|
void RemoveFrom(Size first)
|
|
{
|
|
K_ASSERT(first >= 0 && first <= len);
|
|
|
|
for (Size i = first; i < len; i++) {
|
|
data[i] = T();
|
|
}
|
|
len = first;
|
|
}
|
|
void RemoveLast(Size count = 1)
|
|
{
|
|
K_ASSERT(count >= 0 && count <= len);
|
|
RemoveFrom(len - count);
|
|
}
|
|
|
|
Span<T> Take() const { return Span<T>((T *)data, len); }
|
|
Span<T> Take(Size offset, Size len) const { return Span<T>((T *)data, N).Take(offset, len); }
|
|
Span<T> TakeAvailable() const { return Span<T>((T *)data + len, N - len); }
|
|
|
|
template <typename U = T>
|
|
Span<U> As() const { return Span<U>((U *)data, len); }
|
|
};
|
|
|
|
template <typename T>
|
|
class HeapArray {
|
|
// StaticAssert(std::is_trivially_copyable<T>::value);
|
|
|
|
public:
|
|
T *ptr = nullptr;
|
|
Size len = 0;
|
|
Size capacity = 0;
|
|
Allocator *allocator = nullptr;
|
|
|
|
typedef T value_type;
|
|
typedef T *iterator_type;
|
|
|
|
HeapArray() = default;
|
|
HeapArray(Allocator *alloc, Size min_capacity = 0) : allocator(alloc)
|
|
{ SetCapacity(min_capacity); }
|
|
HeapArray(Size min_capacity) { Reserve(min_capacity); }
|
|
HeapArray(std::initializer_list<T> l)
|
|
{
|
|
Reserve(l.size());
|
|
for (const T &it: l) {
|
|
ptr[len++] = it;
|
|
}
|
|
}
|
|
~HeapArray() { Clear(); }
|
|
|
|
HeapArray(HeapArray &&other) { *this = std::move(other); }
|
|
HeapArray &operator=(HeapArray &&other)
|
|
{
|
|
Clear();
|
|
MemMove(this, &other, K_SIZE(other));
|
|
MemSet(&other, 0, K_SIZE(other));
|
|
return *this;
|
|
}
|
|
HeapArray(const HeapArray &other) { *this = other; }
|
|
HeapArray &operator=(const HeapArray &other)
|
|
{
|
|
RemoveFrom(0);
|
|
Grow(other.capacity);
|
|
if constexpr(!std::is_trivial<T>::value) {
|
|
for (Size i = 0; i < other.len; i++) {
|
|
ptr[i] = other.ptr[i];
|
|
}
|
|
} else {
|
|
MemCpy(ptr, other.ptr, other.len * K_SIZE(*ptr));
|
|
}
|
|
len = other.len;
|
|
return *this;
|
|
}
|
|
|
|
void Clear()
|
|
{
|
|
RemoveFrom(0);
|
|
SetCapacity(0);
|
|
}
|
|
|
|
operator Span<T>() { return Span<T>(ptr, len); }
|
|
operator Span<const T>() const { return Span<const T>(ptr, len); }
|
|
|
|
T *begin() { return ptr; }
|
|
const T *begin() const { return ptr; }
|
|
T *end() { return ptr + len; }
|
|
const T *end() const { return ptr + len; }
|
|
|
|
Size Available() const { return capacity - len; }
|
|
|
|
T &operator[](Size idx)
|
|
{
|
|
K_ASSERT(idx >= 0 && idx < len);
|
|
return ptr[idx];
|
|
}
|
|
const T &operator[](Size idx) const
|
|
{
|
|
K_ASSERT(idx >= 0 && idx < len);
|
|
return ptr[idx];
|
|
}
|
|
|
|
bool operator==(const HeapArray &other) const
|
|
{
|
|
if (len != other.len)
|
|
return false;
|
|
|
|
for (Size i = 0; i < len; i++) {
|
|
if (ptr[i] != other.ptr[i])
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
bool operator!=(const HeapArray &other) const { return !(*this == other); }
|
|
|
|
void SetCapacity(Size new_capacity)
|
|
{
|
|
K_ASSERT(new_capacity >= 0);
|
|
|
|
if (new_capacity != capacity) {
|
|
if (len > new_capacity) {
|
|
for (Size i = new_capacity; i < len; i++) {
|
|
ptr[i].~T();
|
|
}
|
|
len = new_capacity;
|
|
}
|
|
|
|
ptr = (T *)ResizeRaw(allocator, ptr, capacity * K_SIZE(T), new_capacity * K_SIZE(T));
|
|
capacity = new_capacity;
|
|
}
|
|
}
|
|
|
|
void Reserve(Size min_capacity)
|
|
{
|
|
if (min_capacity > capacity) {
|
|
SetCapacity(min_capacity);
|
|
}
|
|
}
|
|
|
|
T *Grow(Size reserve_capacity = 1)
|
|
{
|
|
K_ASSERT(capacity >= 0);
|
|
K_ASSERT(reserve_capacity >= 0);
|
|
K_ASSERT((size_t)capacity + (size_t)reserve_capacity <= K_SIZE_MAX);
|
|
|
|
if (reserve_capacity > capacity - len) {
|
|
Size needed = capacity + reserve_capacity;
|
|
|
|
Size new_capacity;
|
|
if (needed <= K_HEAPARRAY_BASE_CAPACITY) {
|
|
new_capacity = K_HEAPARRAY_BASE_CAPACITY;
|
|
} else {
|
|
new_capacity = (Size)((double)(needed - 1) * K_HEAPARRAY_GROWTH_FACTOR);
|
|
}
|
|
|
|
SetCapacity(new_capacity);
|
|
}
|
|
|
|
return ptr + len;
|
|
}
|
|
|
|
void Trim(Size extra_capacity = 0) { SetCapacity(len + extra_capacity); }
|
|
|
|
T *AppendDefault(Size count = 1)
|
|
{
|
|
Grow(count);
|
|
|
|
T *first = ptr + len;
|
|
if constexpr(!std::is_trivial<T>::value) {
|
|
for (Size i = 0; i < count; i++) {
|
|
new (ptr + len) T();
|
|
len++;
|
|
}
|
|
} else {
|
|
MemSet(first, 0, count * K_SIZE(T));
|
|
len += count;
|
|
}
|
|
|
|
return first;
|
|
}
|
|
|
|
T *Append(const T &value)
|
|
{
|
|
Grow();
|
|
|
|
T *first = ptr + len;
|
|
if constexpr(!std::is_trivial<T>::value) {
|
|
new (ptr + len) T;
|
|
}
|
|
ptr[len++] = value;
|
|
return first;
|
|
}
|
|
T *Append(T &&value)
|
|
{
|
|
Grow();
|
|
|
|
T *first = ptr + len;
|
|
if constexpr(!std::is_trivial<T>::value) {
|
|
new (ptr + len) T;
|
|
}
|
|
ptr[len++] = std::move(value);
|
|
return first;
|
|
}
|
|
T *Append(Span<const T> values)
|
|
{
|
|
Grow(values.len);
|
|
|
|
T *first = ptr + len;
|
|
for (const T &value: values) {
|
|
if constexpr(!std::is_trivial<T>::value) {
|
|
new (ptr + len) T;
|
|
}
|
|
ptr[len++] = value;
|
|
}
|
|
return first;
|
|
}
|
|
|
|
void RemoveFrom(Size first)
|
|
{
|
|
K_ASSERT(first >= 0 && first <= len);
|
|
|
|
if constexpr(!std::is_trivial<T>::value) {
|
|
for (Size i = first; i < len; i++) {
|
|
ptr[i].~T();
|
|
}
|
|
}
|
|
len = first;
|
|
}
|
|
void RemoveLast(Size count = 1)
|
|
{
|
|
K_ASSERT(count >= 0 && count <= len);
|
|
RemoveFrom(len - count);
|
|
}
|
|
|
|
Span<T> Take() const { return Span<T>(ptr, len); }
|
|
Span<T> Take(Size offset, Size len) const { return Span<T>(ptr, this->len).Take(offset, len); }
|
|
Span<T> TakeAvailable() const { return Span<T>((T *)ptr + len, capacity - len); }
|
|
|
|
Span<T> Leak()
|
|
{
|
|
Span<T> span = *this;
|
|
|
|
ptr = nullptr;
|
|
len = 0;
|
|
capacity = 0;
|
|
|
|
return span;
|
|
}
|
|
Span<T> TrimAndLeak(Size extra_capacity = 0)
|
|
{
|
|
Trim(extra_capacity);
|
|
return Leak();
|
|
}
|
|
|
|
template <typename U = T>
|
|
Span<U> As() const { return Span<U>((U *)ptr, len); }
|
|
};
|
|
|
|
template <typename T, Size BucketSize = 64, typename AllocatorType = BlockAllocator>
|
|
class BucketArray {
|
|
K_DELETE_COPY(BucketArray)
|
|
|
|
public:
|
|
struct Bucket {
|
|
T *values;
|
|
AllocatorType allocator;
|
|
};
|
|
|
|
template <typename U>
|
|
class Iterator {
|
|
public:
|
|
typedef std::bidirectional_iterator_tag iterator_category;
|
|
typedef Size value_type;
|
|
typedef Size difference_type;
|
|
typedef Iterator *pointer;
|
|
typedef Iterator &reference;
|
|
|
|
U *queue = nullptr;
|
|
Size bucket_idx;
|
|
Size bucket_offset;
|
|
Bucket *bucket;
|
|
Bucket *next_bucket;
|
|
|
|
Iterator() = default;
|
|
Iterator(U *queue, Size bucket_idx, Size bucket_offset)
|
|
: queue(queue), bucket_idx(bucket_idx), bucket_offset(bucket_offset),
|
|
bucket(GetBucketSafe(bucket_idx)), next_bucket(GetBucketSafe(bucket_idx + 1)) {}
|
|
|
|
T *operator->() { return &bucket->values[bucket_offset]; }
|
|
const T *operator->() const { return &bucket->values[bucket_offset]; }
|
|
T &operator*() { return bucket->values[bucket_offset]; }
|
|
const T &operator*() const { return bucket->values[bucket_offset]; }
|
|
|
|
Iterator &operator++()
|
|
{
|
|
if (++bucket_offset >= BucketSize) {
|
|
bucket_idx++;
|
|
bucket_offset = 0;
|
|
|
|
// Allow iterator to go before start temporarily
|
|
if (bucket_idx < 0) [[unlikely]]
|
|
return *this;
|
|
|
|
if (next_bucket) {
|
|
// We support deletion of all values up to (and including) the current one.
|
|
// When the user does that, some or all front buckets may be gone, but we can
|
|
// use next_bucket to fix bucket_idx.
|
|
while (bucket_idx >= queue->buckets.len ||
|
|
queue->buckets[bucket_idx] != next_bucket) {
|
|
bucket_idx--;
|
|
}
|
|
}
|
|
|
|
bucket = GetBucketSafe(bucket_idx);
|
|
next_bucket = GetBucketSafe(bucket_idx + 1);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
Iterator operator++(int)
|
|
{
|
|
Iterator ret = *this;
|
|
++(*this);
|
|
return ret;
|
|
}
|
|
|
|
Iterator &operator--()
|
|
{
|
|
if (--bucket_offset < 0) {
|
|
bucket_idx--;
|
|
bucket_offset = BucketSize - 1;
|
|
|
|
// Allow iterator to go before start temporarily
|
|
if (bucket_idx >= 0) [[unlikely]] {
|
|
bucket = (bucket_idx >= 0) ? GetBucketSafe(bucket_idx) : nullptr;
|
|
next_bucket = (bucket_idx >= 0) ? GetBucketSafe(bucket_idx + 1) : nullptr;
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
Iterator operator--(int)
|
|
{
|
|
Iterator ret = *this;
|
|
--(*this);
|
|
return ret;
|
|
}
|
|
|
|
bool operator==(const Iterator &other) const
|
|
{ return queue == other.queue && bucket == other.bucket &&
|
|
bucket_offset == other.bucket_offset; }
|
|
bool operator!=(const Iterator &other) const { return !(*this == other); }
|
|
|
|
private:
|
|
Bucket *GetBucketSafe(Size idx)
|
|
{ return idx < queue->buckets.len ? queue->buckets[idx] : nullptr; }
|
|
};
|
|
|
|
HeapArray<Bucket *> buckets;
|
|
Size offset = 0;
|
|
Size count = 0;
|
|
|
|
typedef T value_type;
|
|
typedef Iterator<BucketArray> iterator_type;
|
|
|
|
BucketArray() {}
|
|
BucketArray(std::initializer_list<T> l)
|
|
{
|
|
for (const T &value: l) {
|
|
Append(value);
|
|
}
|
|
}
|
|
~BucketArray() { ClearBucketsAndValues(); }
|
|
|
|
BucketArray(BucketArray &&other) { *this = std::move(other); }
|
|
BucketArray &operator=(BucketArray &&other)
|
|
{
|
|
ClearBucketsAndValues();
|
|
MemMove(this, &other, K_SIZE(other));
|
|
MemSet(&other, 0, K_SIZE(other));
|
|
return *this;
|
|
}
|
|
|
|
void Clear()
|
|
{
|
|
ClearBucketsAndValues();
|
|
|
|
offset = 0;
|
|
count = 0;
|
|
}
|
|
|
|
iterator_type begin() { return iterator_type(this, 0, offset); }
|
|
Iterator<const BucketArray<T, BucketSize>> begin() const { return Iterator<const BucketArray>(this, 0, offset); }
|
|
iterator_type end()
|
|
{
|
|
Size end_idx = offset + count;
|
|
Size bucket_idx = end_idx / BucketSize;
|
|
Size bucket_offset = end_idx % BucketSize;
|
|
|
|
return iterator_type(this, bucket_idx, bucket_offset);
|
|
}
|
|
Iterator<const BucketArray<T, BucketSize>> end() const
|
|
{
|
|
Size end_idx = offset + count;
|
|
Size bucket_idx = end_idx / BucketSize;
|
|
Size bucket_offset = end_idx % BucketSize;
|
|
|
|
return Iterator<const BucketArray>(this, bucket_idx, bucket_offset);
|
|
}
|
|
|
|
const T &operator[](Size idx) const
|
|
{
|
|
K_ASSERT(idx >= 0 && idx < count);
|
|
|
|
idx += offset;
|
|
Size bucket_idx = idx / BucketSize;
|
|
Size bucket_offset = idx % BucketSize;
|
|
|
|
return buckets[bucket_idx]->values[bucket_offset];
|
|
}
|
|
T &operator[](Size idx) { return (T &)(*(const BucketArray *)this)[idx]; }
|
|
|
|
T *AppendDefault(Allocator **out_alloc = nullptr)
|
|
{
|
|
Size bucket_idx = (offset + count) / BucketSize;
|
|
Size bucket_offset = (offset + count) % BucketSize;
|
|
|
|
if (bucket_idx >= buckets.len) {
|
|
Bucket *new_bucket = AllocateOne<Bucket>(buckets.allocator);
|
|
new (&new_bucket->allocator) AllocatorType();
|
|
new_bucket->values = (T *)AllocateRaw(&new_bucket->allocator, BucketSize * K_SIZE(T));
|
|
|
|
buckets.Append(new_bucket);
|
|
}
|
|
|
|
T *first = buckets[bucket_idx]->values + bucket_offset;
|
|
new (first) T();
|
|
|
|
count++;
|
|
|
|
if (out_alloc) {
|
|
*out_alloc = &buckets[bucket_idx]->allocator;
|
|
}
|
|
return first;
|
|
}
|
|
|
|
T *Append(const T &value, Allocator **out_alloc = nullptr)
|
|
{
|
|
T *it = AppendDefault(out_alloc);
|
|
*it = value;
|
|
return it;
|
|
}
|
|
|
|
void RemoveFrom(Size from)
|
|
{
|
|
K_ASSERT(from >= 0 && from <= count);
|
|
|
|
if (from == count)
|
|
return;
|
|
if (!from) {
|
|
Clear();
|
|
return;
|
|
}
|
|
|
|
Size start_idx = offset + from;
|
|
Size start_bucket_idx = start_idx / BucketSize;
|
|
Size start_bucket_offset = start_idx % BucketSize;
|
|
|
|
iterator_type from_it(this, start_bucket_idx, start_bucket_offset);
|
|
DeleteValues(from_it, end());
|
|
|
|
Size delete_idx = start_bucket_idx + !!start_bucket_offset;
|
|
for (Size i = delete_idx; i < buckets.len; i++) {
|
|
DeleteBucket(buckets[i]);
|
|
}
|
|
buckets.RemoveFrom(delete_idx);
|
|
|
|
count = from;
|
|
}
|
|
void RemoveLast(Size n = 1)
|
|
{
|
|
K_ASSERT(n >= 0 && n <= count);
|
|
RemoveFrom(count - n);
|
|
}
|
|
|
|
void RemoveFirst(Size n = 1)
|
|
{
|
|
K_ASSERT(n >= 0 && n <= count);
|
|
|
|
if (n == count) {
|
|
Clear();
|
|
return;
|
|
}
|
|
|
|
Size end_idx = offset + n;
|
|
Size end_bucket_idx = end_idx / BucketSize;
|
|
Size end_bucket_offset = end_idx % BucketSize;
|
|
|
|
iterator_type until_it(this, end_bucket_idx, end_bucket_offset);
|
|
DeleteValues(begin(), until_it);
|
|
|
|
if (end_bucket_idx) {
|
|
for (Size i = 0; i < end_bucket_idx; i++) {
|
|
DeleteBucket(buckets[i]);
|
|
}
|
|
MemMove(&buckets[0], &buckets[end_bucket_idx],
|
|
(buckets.len - end_bucket_idx) * K_SIZE(Bucket *));
|
|
buckets.RemoveLast(end_bucket_idx);
|
|
}
|
|
|
|
offset = (offset + n) % BucketSize;
|
|
count -= n;
|
|
}
|
|
|
|
void RemoveFrom(const iterator_type &it)
|
|
{
|
|
if (it == end())
|
|
return;
|
|
if (it == begin()) {
|
|
Clear();
|
|
return;
|
|
}
|
|
|
|
DeleteValues(it, end());
|
|
|
|
Size delete_idx = it.bucket_idx + !!it.bucket_offset;
|
|
for (Size i = delete_idx; i < buckets.len; i++) {
|
|
DeleteBucket(buckets[i]);
|
|
}
|
|
buckets.RemoveFrom(delete_idx);
|
|
|
|
count = (it.bucket_idx * BucketSize) + it.bucket_offset - offset;
|
|
|
|
K_ASSERT(it == end());
|
|
}
|
|
void RemoveFrom(const Iterator<const BucketArray<T, BucketSize>> &it) { return RemoveFrom((iterator_type)it); }
|
|
|
|
void RemoveUntil(const iterator_type &it)
|
|
{
|
|
if (it == begin())
|
|
return;
|
|
if (it == end()) {
|
|
Clear();
|
|
return;
|
|
}
|
|
|
|
DeleteValues(begin(), it);
|
|
|
|
if (it.bucket_idx) {
|
|
for (Size i = 0; i < it.bucket_idx; i++) {
|
|
DeleteBucket(buckets[i]);
|
|
}
|
|
MemMove(&buckets[0], &buckets[it.bucket_idx],
|
|
(buckets.len - it.bucket_idx) * K_SIZE(Bucket *));
|
|
buckets.RemoveLast(it.bucket_idx);
|
|
}
|
|
|
|
Size count = it.bucket_idx * BucketSize + it.bucket_offset - offset;
|
|
|
|
offset = (offset + count) % BucketSize;
|
|
count -= count;
|
|
}
|
|
void RemoveUntil(const Iterator<const BucketArray<T, BucketSize>> &it) { return RemoveUntil((iterator_type)it); }
|
|
|
|
void Trim()
|
|
{
|
|
buckets.Trim();
|
|
}
|
|
|
|
private:
|
|
void ClearBucketsAndValues()
|
|
{
|
|
DeleteValues(begin(), end());
|
|
|
|
for (Bucket *bucket: buckets) {
|
|
DeleteBucket(bucket);
|
|
}
|
|
buckets.Clear();
|
|
}
|
|
|
|
void DeleteValues([[maybe_unused]] iterator_type begin,
|
|
[[maybe_unused]] iterator_type end)
|
|
{
|
|
if constexpr(!std::is_trivial<T>::value) {
|
|
for (iterator_type it = begin; it != end; ++it) {
|
|
it->~T();
|
|
}
|
|
}
|
|
}
|
|
|
|
void DeleteBucket(Bucket *bucket)
|
|
{
|
|
bucket->allocator.~AllocatorType();
|
|
ReleaseOne(buckets.allocator, bucket);
|
|
}
|
|
};
|
|
|
|
template <typename KeyType, typename ValueType,
|
|
typename Handler = typename std::remove_pointer<ValueType>::type::HashHandler>
|
|
class HashTable {
|
|
public:
|
|
template <typename T>
|
|
class Iterator {
|
|
public:
|
|
typedef std::forward_iterator_tag iterator_category;
|
|
typedef ValueType value_type;
|
|
typedef Size difference_type;
|
|
typedef Iterator *pointer;
|
|
typedef Iterator &reference;
|
|
|
|
T *table = nullptr;
|
|
Size offset;
|
|
|
|
Iterator() = default;
|
|
Iterator(T *table, Size offset)
|
|
: table(table), offset(offset - 1) { operator++(); }
|
|
|
|
ValueType &operator*()
|
|
{
|
|
K_ASSERT(!table->IsEmpty(offset));
|
|
return table->data[offset];
|
|
}
|
|
const ValueType &operator*() const
|
|
{
|
|
K_ASSERT(!table->IsEmpty(offset));
|
|
return table->data[offset];
|
|
}
|
|
|
|
Iterator &operator++()
|
|
{
|
|
K_ASSERT(offset < table->capacity);
|
|
while (++offset < table->capacity && table->IsEmpty(offset));
|
|
return *this;
|
|
}
|
|
|
|
Iterator operator++(int)
|
|
{
|
|
Iterator ret = *this;
|
|
++(*this);
|
|
return ret;
|
|
}
|
|
|
|
// Beware, in some cases a previous value may be seen again after this action
|
|
void Remove()
|
|
{
|
|
table->Remove(&table->data[offset]);
|
|
offset--;
|
|
}
|
|
|
|
bool operator==(const Iterator &other) const
|
|
{ return table == other.table && offset == other.offset; }
|
|
bool operator!=(const Iterator &other) const { return !(*this == other); }
|
|
};
|
|
|
|
typedef Size value_type;
|
|
typedef Iterator<HashTable> iterator_type;
|
|
|
|
size_t *used = nullptr;
|
|
ValueType *data = nullptr;
|
|
Size count = 0;
|
|
Size capacity = 0;
|
|
Allocator *allocator = nullptr;
|
|
|
|
HashTable() = default;
|
|
HashTable(std::initializer_list<ValueType> l)
|
|
{
|
|
for (const ValueType &value: l) {
|
|
Set(value);
|
|
}
|
|
}
|
|
~HashTable() { Clear(); }
|
|
|
|
HashTable(HashTable &&other) { *this = std::move(other); }
|
|
HashTable &operator=(HashTable &&other)
|
|
{
|
|
Clear();
|
|
MemMove(this, &other, K_SIZE(other));
|
|
MemSet(&other, 0, K_SIZE(other));
|
|
return *this;
|
|
}
|
|
HashTable(const HashTable &other) { *this = other; }
|
|
HashTable &operator=(const HashTable &other)
|
|
{
|
|
Clear();
|
|
for (const ValueType &value: other) {
|
|
Set(value);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
void Clear()
|
|
{
|
|
if constexpr(!std::is_trivial<ValueType>::value) {
|
|
for (Size i = 0; i < capacity; i++) {
|
|
data[i].~ValueType();
|
|
}
|
|
}
|
|
|
|
count = 0;
|
|
Rehash(0);
|
|
}
|
|
|
|
void RemoveAll()
|
|
{
|
|
if constexpr(!std::is_trivial<ValueType>::value) {
|
|
for (Size i = 0; i < capacity; i++) {
|
|
data[i].~ValueType();
|
|
}
|
|
}
|
|
|
|
count = 0;
|
|
if (used) {
|
|
size_t len = (size_t)(capacity + (K_SIZE(size_t) * 8) - 1) / K_SIZE(size_t);
|
|
MemSet(used, 0, len);
|
|
}
|
|
}
|
|
|
|
Iterator<HashTable> begin() { return Iterator<HashTable>(this, 0); }
|
|
Iterator<const HashTable> begin() const { return Iterator<const HashTable>(this, 0); }
|
|
Iterator<HashTable> end() { return Iterator<HashTable>(this, capacity); }
|
|
Iterator<const HashTable> end() const { return Iterator<const HashTable>(this, capacity); }
|
|
|
|
template <typename T = KeyType>
|
|
ValueType *Find(const T &key)
|
|
{ return (ValueType *)((const HashTable *)this)->Find(key); }
|
|
template <typename T = KeyType>
|
|
const ValueType *Find(const T &key) const
|
|
{
|
|
if (!capacity)
|
|
return nullptr;
|
|
|
|
uint64_t hash = Handler::HashKey(key);
|
|
Size idx = HashToIndex(hash);
|
|
return Find(&idx, key);
|
|
}
|
|
template <typename T = KeyType>
|
|
ValueType FindValue(const T &key, const ValueType &default_value)
|
|
{ return (ValueType)((const HashTable *)this)->FindValue(key, default_value); }
|
|
template <typename T = KeyType>
|
|
const ValueType FindValue(const T &key, const ValueType &default_value) const
|
|
{
|
|
const ValueType *it = Find(key);
|
|
return it ? *it : default_value;
|
|
}
|
|
|
|
ValueType *Set(const ValueType &value)
|
|
{
|
|
const KeyType &key = Handler::GetKey(value);
|
|
|
|
bool inserted;
|
|
ValueType *ptr = Insert(key, &inserted);
|
|
|
|
*ptr = value;
|
|
|
|
return ptr;
|
|
}
|
|
ValueType *SetDefault(const KeyType &key)
|
|
{
|
|
bool inserted;
|
|
ValueType *ptr = Insert(key, &inserted);
|
|
|
|
if (!inserted) {
|
|
ptr->~ValueType();
|
|
}
|
|
new (ptr) ValueType();
|
|
|
|
return ptr;
|
|
}
|
|
|
|
ValueType *InsertOrGet(const ValueType &value, bool *out_inserted = nullptr)
|
|
{
|
|
const KeyType &key = Handler::GetKey(value);
|
|
|
|
bool inserted;
|
|
ValueType *ptr = Insert(key, &inserted);
|
|
|
|
if (inserted) {
|
|
*ptr = value;
|
|
}
|
|
|
|
if (out_inserted) {
|
|
*out_inserted = inserted;
|
|
}
|
|
return ptr;
|
|
}
|
|
ValueType *InsertOrGetDefault(const KeyType &key, bool *out_inserted = nullptr)
|
|
{
|
|
bool inserted;
|
|
ValueType *ptr = Insert(key, &inserted);
|
|
|
|
if (inserted) {
|
|
new (ptr) ValueType();
|
|
}
|
|
|
|
if (out_inserted) {
|
|
*out_inserted = inserted;
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
void Remove(ValueType *it)
|
|
{
|
|
if (!it)
|
|
return;
|
|
|
|
Size clear_idx = it - data;
|
|
K_ASSERT(!IsEmpty(clear_idx));
|
|
|
|
it->~ValueType();
|
|
count--;
|
|
MarkEmpty(clear_idx);
|
|
|
|
// Move following slots if needed
|
|
for (Size idx = NextIndex(clear_idx); !IsEmpty(idx); idx = NextIndex(idx)) {
|
|
Size real_idx = KeyToIndex(Handler::GetKey(data[idx]));
|
|
|
|
if (clear_idx <= idx) {
|
|
if (clear_idx < real_idx && real_idx <= idx)
|
|
continue;
|
|
} else {
|
|
if (real_idx <= idx || clear_idx < real_idx)
|
|
continue;
|
|
}
|
|
|
|
MarkUsed(clear_idx);
|
|
MarkEmpty(idx);
|
|
MemMove(&data[clear_idx], &data[idx], K_SIZE(*data));
|
|
|
|
clear_idx = idx;
|
|
}
|
|
|
|
new (&data[clear_idx]) ValueType();
|
|
}
|
|
template <typename T = KeyType>
|
|
void Remove(const T &key) { Remove(Find(key)); }
|
|
|
|
void Trim()
|
|
{
|
|
if (count) {
|
|
Size new_capacity = (Size)1 << (64 - CountLeadingZeros((uint64_t)count));
|
|
|
|
if (new_capacity < K_HASHTABLE_BASE_CAPACITY) {
|
|
new_capacity = K_HASHTABLE_BASE_CAPACITY;
|
|
} else if (count > (double)new_capacity * K_HASHTABLE_MAX_LOAD_FACTOR) {
|
|
new_capacity *= 2;
|
|
}
|
|
|
|
Rehash(new_capacity);
|
|
} else {
|
|
Rehash(0);
|
|
}
|
|
}
|
|
|
|
private:
|
|
template <typename T = KeyType>
|
|
ValueType *Find(Size *idx, const T &key)
|
|
{ return (ValueType *)((const HashTable *)this)->Find(idx, key); }
|
|
template <typename T = KeyType>
|
|
const ValueType *Find(Size *idx, const T &key) const
|
|
{
|
|
if constexpr(std::is_pointer<ValueType>::value) {
|
|
while (data[*idx]) {
|
|
const KeyType &it_key = Handler::GetKey(data[*idx]);
|
|
if (Handler::TestKeys(it_key, key))
|
|
return &data[*idx];
|
|
*idx = NextIndex(*idx);
|
|
}
|
|
return nullptr;
|
|
} else {
|
|
while (!IsEmpty(*idx)) {
|
|
const KeyType &it_key = Handler::GetKey(data[*idx]);
|
|
if (Handler::TestKeys(it_key, key))
|
|
return &data[*idx];
|
|
*idx = NextIndex(*idx);
|
|
}
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
ValueType *Insert(const KeyType &key, bool *out_inserted)
|
|
{
|
|
uint64_t hash = Handler::HashKey(key);
|
|
|
|
if (capacity) {
|
|
Size idx = HashToIndex(hash);
|
|
ValueType *it = Find(&idx, key);
|
|
if (!it) {
|
|
if (count >= (Size)((double)capacity * K_HASHTABLE_MAX_LOAD_FACTOR)) {
|
|
Rehash(capacity << 1);
|
|
idx = HashToIndex(hash);
|
|
while (!IsEmpty(idx)) {
|
|
idx = NextIndex(idx);
|
|
}
|
|
}
|
|
count++;
|
|
MarkUsed(idx);
|
|
|
|
*out_inserted = true;
|
|
return &data[idx];
|
|
} else {
|
|
*out_inserted = false;
|
|
return it;
|
|
}
|
|
} else {
|
|
Rehash(K_HASHTABLE_BASE_CAPACITY);
|
|
|
|
Size idx = HashToIndex(hash);
|
|
count++;
|
|
MarkUsed(idx);
|
|
|
|
*out_inserted = true;
|
|
return &data[idx];
|
|
}
|
|
}
|
|
|
|
void Rehash(Size new_capacity)
|
|
{
|
|
if (new_capacity == capacity)
|
|
return;
|
|
K_ASSERT(count <= new_capacity);
|
|
|
|
size_t *old_used = used;
|
|
ValueType *old_data = data;
|
|
Size old_capacity = capacity;
|
|
|
|
if (new_capacity) {
|
|
used = (size_t *)AllocateRaw(allocator,
|
|
(new_capacity + (K_SIZE(size_t) * 8) - 1) / K_SIZE(size_t),
|
|
(int)AllocFlag::Zero);
|
|
data = (ValueType *)AllocateRaw(allocator, new_capacity * K_SIZE(ValueType));
|
|
for (Size i = 0; i < new_capacity; i++) {
|
|
new (&data[i]) ValueType();
|
|
}
|
|
capacity = new_capacity;
|
|
|
|
for (Size i = 0; i < old_capacity; i++) {
|
|
if (!IsEmpty(old_used, i)) {
|
|
Size new_idx = KeyToIndex(Handler::GetKey(old_data[i]));
|
|
while (!IsEmpty(new_idx)) {
|
|
new_idx = NextIndex(new_idx);
|
|
}
|
|
MarkUsed(new_idx);
|
|
data[new_idx] = old_data[i];
|
|
}
|
|
}
|
|
} else {
|
|
used = nullptr;
|
|
data = nullptr;
|
|
capacity = 0;
|
|
}
|
|
|
|
ReleaseRaw(allocator, old_used, (old_capacity + (K_SIZE(size_t) * 8) - 1) / K_SIZE(size_t));
|
|
ReleaseRaw(allocator, old_data, old_capacity * K_SIZE(ValueType));
|
|
}
|
|
|
|
inline void MarkUsed(Size idx)
|
|
{
|
|
used[idx / (K_SIZE(size_t) * 8)] |= (1ull << (idx % (K_SIZE(size_t) * 8)));
|
|
}
|
|
inline void MarkEmpty(Size idx)
|
|
{
|
|
used[idx / (K_SIZE(size_t) * 8)] &= ~(1ull << (idx % (K_SIZE(size_t) * 8)));
|
|
}
|
|
|
|
inline bool IsEmpty(size_t *used, Size idx) const
|
|
{
|
|
bool empty = !(used[idx / (K_SIZE(size_t) * 8)] & (1ull << (idx % (K_SIZE(size_t) * 8))));
|
|
return empty;
|
|
}
|
|
inline bool IsEmpty(Size idx) const { return IsEmpty(used, idx); }
|
|
|
|
inline Size HashToIndex(uint64_t hash) const
|
|
{
|
|
return (Size)(hash & (uint64_t)(capacity - 1));
|
|
}
|
|
inline Size KeyToIndex(const KeyType &key) const
|
|
{
|
|
uint64_t hash = Handler::HashKey(key);
|
|
return HashToIndex(hash);
|
|
}
|
|
|
|
inline Size NextIndex(Size idx) const
|
|
{
|
|
return (idx + 1) & (capacity - 1);
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
class HashTraits {
|
|
public:
|
|
static constexpr uint64_t Hash(const T &key) { return key.Hash(); }
|
|
static constexpr bool Test(const T &key1, const T &key2) { return key1 == key2; }
|
|
};
|
|
|
|
// Stole the Hash function from Thomas Wang (see here: https://gist.github.com/badboy/6267743)
|
|
#define DEFINE_INTEGER_HASH_TRAITS_32(Type, ...) \
|
|
template <> \
|
|
class HashTraits<Type> { \
|
|
public: \
|
|
static __VA_ARGS__ uint64_t Hash(Type key) \
|
|
{ \
|
|
uint32_t hash = (uint32_t)key; \
|
|
\
|
|
hash = (hash ^ 61) ^ (hash >> 16); \
|
|
hash += hash << 3; \
|
|
hash ^= hash >> 4; \
|
|
hash *= 0x27D4EB2D; \
|
|
hash ^= hash >> 15; \
|
|
\
|
|
return (uint64_t)hash; \
|
|
} \
|
|
\
|
|
static __VA_ARGS__ bool Test(Type key1, Type key2) { return key1 == key2; } \
|
|
}
|
|
#define DEFINE_INTEGER_HASH_TRAITS_64(Type, ...) \
|
|
template <> \
|
|
class HashTraits<Type> { \
|
|
public: \
|
|
static __VA_ARGS__ uint64_t Hash(Type key) \
|
|
{ \
|
|
uint64_t hash = (uint64_t)key; \
|
|
\
|
|
hash = (~hash) + (hash << 18); \
|
|
hash ^= hash >> 31; \
|
|
hash *= 21; \
|
|
hash ^= hash >> 11; \
|
|
hash += hash << 6; \
|
|
hash ^= hash >> 22; \
|
|
\
|
|
return hash; \
|
|
} \
|
|
\
|
|
static __VA_ARGS__ bool Test(Type key1, Type key2) { return key1 == key2; } \
|
|
}
|
|
|
|
DEFINE_INTEGER_HASH_TRAITS_32(char, constexpr);
|
|
DEFINE_INTEGER_HASH_TRAITS_32(unsigned char, constexpr);
|
|
DEFINE_INTEGER_HASH_TRAITS_32(short, constexpr);
|
|
DEFINE_INTEGER_HASH_TRAITS_32(unsigned short, constexpr);
|
|
DEFINE_INTEGER_HASH_TRAITS_32(int, constexpr);
|
|
DEFINE_INTEGER_HASH_TRAITS_32(unsigned int, constexpr);
|
|
#if defined(__LP64__)
|
|
DEFINE_INTEGER_HASH_TRAITS_64(long, constexpr);
|
|
DEFINE_INTEGER_HASH_TRAITS_64(unsigned long, constexpr);
|
|
#else
|
|
DEFINE_INTEGER_HASH_TRAITS_32(long, constexpr);
|
|
DEFINE_INTEGER_HASH_TRAITS_32(unsigned long, constexpr);
|
|
#endif
|
|
DEFINE_INTEGER_HASH_TRAITS_64(long long, constexpr);
|
|
DEFINE_INTEGER_HASH_TRAITS_64(unsigned long long, constexpr);
|
|
#if K_SIZE_MAX == INT64_MAX
|
|
DEFINE_INTEGER_HASH_TRAITS_64(void *);
|
|
DEFINE_INTEGER_HASH_TRAITS_64(const void *);
|
|
#else
|
|
DEFINE_INTEGER_HASH_TRAITS_32(void *);
|
|
DEFINE_INTEGER_HASH_TRAITS_32(const void *);
|
|
#endif
|
|
|
|
#undef DEFINE_INTEGER_HASH_TRAITS_32
|
|
#undef DEFINE_INTEGER_HASH_TRAITS_64
|
|
|
|
// MurmurHash2
|
|
static constexpr inline uint64_t HashStr(Span<const char> str)
|
|
{
|
|
const uint64_t Seed = 0;
|
|
const uint64_t Mult = (((uint64_t)0xc6a4a793ull) << 32ull) + (uint64_t)0x5bd1e995ull;
|
|
|
|
const auto unaligned_load =
|
|
#if __cplusplus >= 202002L && (__GNUC__ >= 12 || __clang_major__ >= 16)
|
|
!std::is_constant_evaluated() ?
|
|
[](const char *p) {
|
|
uint64_t result;
|
|
__builtin_memcpy(&result, p, sizeof(result));
|
|
return result;
|
|
} :
|
|
#endif
|
|
[](const char *p) {
|
|
#if defined(K_BIG_ENDIAN)
|
|
uint64_t result = ((uint64_t)p[0] << 56) |
|
|
((uint64_t)p[1] << 48) |
|
|
((uint64_t)p[2] << 40) |
|
|
((uint64_t)p[3] << 32) |
|
|
((uint64_t)p[4] << 24) |
|
|
((uint64_t)p[5] << 16) |
|
|
((uint64_t)p[6] << 8) |
|
|
((uint64_t)p[7] << 0);
|
|
#else
|
|
uint64_t result = ((uint64_t)p[0] << 0) |
|
|
((uint64_t)p[1] << 8) |
|
|
((uint64_t)p[2] << 16) |
|
|
((uint64_t)p[3] << 24) |
|
|
((uint64_t)p[4] << 32) |
|
|
((uint64_t)p[5] << 40) |
|
|
((uint64_t)p[6] << 48) |
|
|
((uint64_t)p[7] << 56);
|
|
#endif
|
|
|
|
return result;
|
|
};
|
|
|
|
const auto load_bytes = [](const char *p, int n) {
|
|
uint64_t result = 0;
|
|
|
|
n--;
|
|
do {
|
|
result = (result << 8) + (uint8_t)p[n];
|
|
} while (--n >= 0);
|
|
|
|
return result;
|
|
};
|
|
const auto shift_mix = [](uint64_t v) { return v ^ (v >> 47); };
|
|
|
|
const char *end = str.ptr + (str.len & ~0x7);
|
|
int remain = (int)(str.len & 0x7);
|
|
|
|
uint64_t hash = Seed ^ (str.len * Mult);
|
|
|
|
for (const char *p = str.ptr; p != end; p += 8) {
|
|
uint64_t u64 = shift_mix(unaligned_load(p) * Mult) * Mult;
|
|
hash = (hash ^ u64) * Mult;
|
|
}
|
|
if (remain) {
|
|
uint64_t u64 = load_bytes(end, remain);
|
|
hash = (hash ^ u64) * Mult;
|
|
}
|
|
|
|
hash = shift_mix(hash) * Mult;
|
|
hash = shift_mix(hash);
|
|
|
|
return hash;
|
|
}
|
|
|
|
static constexpr inline uint64_t HashStr(const char *str)
|
|
{
|
|
Span<const char> span = str;
|
|
return HashStr(span);
|
|
}
|
|
|
|
template <>
|
|
class HashTraits<const char *> {
|
|
public:
|
|
static constexpr uint64_t Hash(Span<const char> key) { return HashStr(key); }
|
|
static constexpr uint64_t Hash(const char *key) { return HashStr(key); }
|
|
|
|
static constexpr bool Test(const char *key1, const char *key2) { return TestStr(key1, key2); }
|
|
static constexpr bool Test(const char *key1, Span<const char> key2) { return key2 == key1; }
|
|
};
|
|
|
|
template <>
|
|
class HashTraits<Span<const char>> {
|
|
public:
|
|
static constexpr uint64_t Hash(Span<const char> key) { return HashStr(key); }
|
|
static constexpr uint64_t Hash(const char *key) { return HashStr(key); }
|
|
|
|
static constexpr bool Test(Span<const char> key1, Span<const char> key2) { return key1 == key2; }
|
|
static constexpr bool Test(Span<const char> key1, const char * key2) { return key1 == key2; }
|
|
};
|
|
|
|
#define K_HASHTABLE_HANDLER_EX_N(Name, ValueType, KeyType, KeyMember, HashFunc, TestFunc) \
|
|
class Name { \
|
|
public: \
|
|
static constexpr KeyType GetKey(const ValueType &value) \
|
|
{ return (KeyType)(value.KeyMember); } \
|
|
static constexpr KeyType GetKey(const ValueType *value) \
|
|
{ return (KeyType)(value->KeyMember); } \
|
|
template <typename TestKey> \
|
|
static constexpr uint64_t HashKey(TestKey key) \
|
|
{ return HashFunc(key); } \
|
|
template <typename TestKey> \
|
|
static constexpr bool TestKeys(KeyType key1, TestKey key2) \
|
|
{ return TestFunc((key1), (key2)); } \
|
|
}
|
|
#define K_HASHTABLE_HANDLER_EX(ValueType, KeyType, KeyMember, HashFunc, TestFunc) \
|
|
K_HASHTABLE_HANDLER_EX_N(HashHandler, ValueType, KeyType, KeyMember, HashFunc, TestFunc)
|
|
#define K_HASHTABLE_HANDLER(ValueType, KeyMember) \
|
|
K_HASHTABLE_HANDLER_EX(ValueType, decltype(ValueType::KeyMember), KeyMember, HashTraits<decltype(ValueType::KeyMember)>::Hash, HashTraits<decltype(ValueType::KeyMember)>::Test)
|
|
#define K_HASHTABLE_HANDLER_N(Name, ValueType, KeyMember) \
|
|
K_HASHTABLE_HANDLER_EX_N(Name, ValueType, decltype(ValueType::KeyMember), KeyMember, HashTraits<decltype(ValueType::KeyMember)>::Hash, HashTraits<decltype(ValueType::KeyMember)>::Test)
|
|
#define K_HASHTABLE_HANDLER_T(ValueType, KeyType, KeyMember) \
|
|
K_HASHTABLE_HANDLER_EX(ValueType, KeyType, KeyMember, HashTraits<KeyType>::Hash, HashTraits<KeyType>::Test)
|
|
#define K_HASHTABLE_HANDLER_NT(Name, ValueType, KeyType, KeyMember) \
|
|
K_HASHTABLE_HANDLER_EX_N(Name, ValueType, KeyType, KeyMember, HashTraits<KeyType>::Hash, HashTraits<KeyType>::Test)
|
|
|
|
template <typename KeyType, typename ValueType>
|
|
class HashMap {
|
|
public:
|
|
struct Bucket {
|
|
KeyType key;
|
|
ValueType value;
|
|
|
|
K_HASHTABLE_HANDLER(Bucket, key);
|
|
};
|
|
|
|
HashTable<KeyType, Bucket> table;
|
|
|
|
HashMap() = default;
|
|
HashMap(std::initializer_list<Bucket> l) : table(l) {}
|
|
|
|
void Clear() { table.Clear(); }
|
|
void RemoveAll() { table.RemoveAll(); }
|
|
|
|
template <typename T = KeyType>
|
|
ValueType *Find(const T &key)
|
|
{ return (ValueType *)((const HashMap *)this)->Find(key); }
|
|
template <typename T = KeyType>
|
|
const ValueType *Find(const T &key) const
|
|
{
|
|
const Bucket *table_it = table.Find(key);
|
|
return table_it ? &table_it->value : nullptr;
|
|
}
|
|
template <typename T = KeyType>
|
|
ValueType FindValue(const T &key, const ValueType &default_value)
|
|
{ return (ValueType)((const HashMap *)this)->FindValue(key, default_value); }
|
|
template <typename T = KeyType>
|
|
const ValueType FindValue(const T &key, const ValueType &default_value) const
|
|
{
|
|
const ValueType *it = Find(key);
|
|
return it ? *it : default_value;
|
|
}
|
|
|
|
ValueType *Set(const KeyType &key, const ValueType &value)
|
|
{ return &table.Set({ key, value })->value; }
|
|
Bucket *SetDefault(const KeyType &key)
|
|
{
|
|
Bucket *table_it = table.SetDefault(key);
|
|
table_it->key = key;
|
|
return table_it;
|
|
}
|
|
|
|
ValueType *InsertOrGet(const KeyType &key, const ValueType &value, bool *out_inserted = nullptr)
|
|
{
|
|
Bucket *ptr = table.InsertOrGet({ key, value }, out_inserted);
|
|
return &ptr->value;
|
|
}
|
|
Bucket *InsertOrGetDefault(const KeyType &key, bool *out_inserted = nullptr)
|
|
{
|
|
bool inserted;
|
|
Bucket *ptr = table.InsertOrGetDefault(key, &inserted);
|
|
|
|
if (inserted) {
|
|
ptr->key = key;
|
|
}
|
|
|
|
if (out_inserted) {
|
|
*out_inserted = inserted;
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
void Remove(ValueType *it)
|
|
{
|
|
if (!it)
|
|
return;
|
|
table.Remove((Bucket *)((uint8_t *)it - offsetof(Bucket, value)));
|
|
}
|
|
void Remove(Bucket *it)
|
|
{
|
|
if (!it)
|
|
return;
|
|
table.Remove(it);
|
|
}
|
|
template <typename T = KeyType>
|
|
void Remove(const KeyType &key) { Remove(Find(key)); }
|
|
|
|
void Trim() { table.Trim(); }
|
|
};
|
|
|
|
template <typename ValueType>
|
|
class HashSet {
|
|
class Handler {
|
|
public:
|
|
static constexpr ValueType GetKey(const ValueType &value) { return value; }
|
|
static constexpr ValueType GetKey(const ValueType *value) { return *value; }
|
|
static constexpr uint64_t HashKey(const ValueType &value)
|
|
{ return HashTraits<ValueType>::Hash(value); }
|
|
static constexpr bool TestKeys(const ValueType &value1, const ValueType &value2)
|
|
{ return HashTraits<ValueType>::Test(value1, value2); }
|
|
};
|
|
|
|
public:
|
|
HashTable<ValueType, ValueType, Handler> table;
|
|
|
|
HashSet() = default;
|
|
HashSet(std::initializer_list<ValueType> l) : table(l) {}
|
|
|
|
void Clear() { table.Clear(); }
|
|
void RemoveAll() { table.RemoveAll(); }
|
|
|
|
template <typename T = ValueType>
|
|
ValueType *Find(const T &value) { return table.Find(value); }
|
|
template <typename T = ValueType>
|
|
const ValueType *Find(const T &value) const { return table.Find(value); }
|
|
template <typename T = ValueType>
|
|
ValueType FindValue(const T &value, const ValueType &default_value)
|
|
{ return table.FindValue(value, default_value); }
|
|
template <typename T = ValueType>
|
|
const ValueType FindValue(const T &value, const ValueType &default_value) const
|
|
{ return table.FindValue(value, default_value); }
|
|
|
|
ValueType *Set(const ValueType &value) { return table.Set(value); }
|
|
|
|
ValueType *InsertOrGet(const ValueType &value, bool *out_inserted = nullptr)
|
|
{ return table.InsertOrGet(value, out_inserted); }
|
|
bool InsertOrFail(const ValueType &value)
|
|
{
|
|
bool inserted;
|
|
InsertOrGet(value, &inserted);
|
|
return inserted;
|
|
}
|
|
|
|
void Remove(ValueType *it) { table.Remove(it); }
|
|
template <typename T = ValueType>
|
|
void Remove(const T &value) { Remove(Find(value)); }
|
|
|
|
void Trim() { table.Trim(); }
|
|
|
|
private:
|
|
};
|
|
|
|
// XXX: Switch to perfect hashing later on
|
|
template <Size N, typename KeyType, typename ValueType>
|
|
class ConstMap {
|
|
public:
|
|
struct Bucket {
|
|
KeyType key;
|
|
ValueType value;
|
|
};
|
|
|
|
size_t used[(N + (K_SIZE(size_t) * 8) - 1) / K_SIZE(size_t)] = {};
|
|
Bucket data[N] = {};
|
|
Size count = 0;
|
|
|
|
constexpr ConstMap(std::initializer_list<Bucket> l)
|
|
{
|
|
K_CRITICAL(l.size() <= N, "ConstMap<%1> cannot store %2 values", N, l.size());
|
|
|
|
for (const Bucket &it: l) {
|
|
Bucket *bucket = Insert(it.key);
|
|
|
|
bucket->key = it.key;
|
|
bucket->value = it.value;
|
|
}
|
|
}
|
|
|
|
template <typename T = KeyType>
|
|
ValueType *Find(const T &key)
|
|
{ return (ValueType *)((const ConstMap *)this)->Find(key); }
|
|
template <typename T = KeyType>
|
|
const ValueType *Find(const T &key) const
|
|
{
|
|
uint64_t hash = HashTraits<KeyType>::Hash(key);
|
|
Size idx = HashToIndex(hash);
|
|
|
|
const Bucket *bucket = Find(&idx, key);
|
|
return bucket ? &bucket->value : nullptr;
|
|
}
|
|
|
|
template <typename T = KeyType>
|
|
ValueType FindValue(const T &key, const ValueType &default_value)
|
|
{ return (ValueType)((const ConstMap *)this)->FindValue(key, default_value); }
|
|
template <typename T = KeyType>
|
|
const ValueType FindValue(const T &key, const ValueType &default_value) const
|
|
{
|
|
const ValueType *it = Find(key);
|
|
return it ? *it : default_value;
|
|
}
|
|
|
|
private:
|
|
template <typename T = KeyType>
|
|
constexpr Bucket *Find(Size *idx, const T &key)
|
|
{ return (Bucket *)((const ConstMap *)this)->Find(idx, key); }
|
|
template <typename T = KeyType>
|
|
constexpr const Bucket *Find(Size *idx, const T &key) const
|
|
{
|
|
while (!IsEmpty(*idx)) {
|
|
if (HashTraits<KeyType>::Test(data[*idx].key, key))
|
|
return &data[*idx];
|
|
*idx = (*idx + 1) & (N - 1);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
constexpr Bucket *Insert(const KeyType &key)
|
|
{
|
|
uint64_t hash = HashTraits<KeyType>::Hash(key);
|
|
Size idx = HashToIndex(hash);
|
|
Bucket *it = Find(&idx, key);
|
|
|
|
if (!it) {
|
|
count++;
|
|
MarkUsed(idx);
|
|
|
|
return &data[idx];
|
|
} else {
|
|
return it;
|
|
}
|
|
}
|
|
|
|
constexpr void MarkUsed(Size idx)
|
|
{
|
|
used[idx / (K_SIZE(size_t) * 8)] |= (1ull << (idx % (K_SIZE(size_t) * 8)));
|
|
}
|
|
constexpr bool IsEmpty(Size idx) const
|
|
{
|
|
bool empty = !(used[idx / (K_SIZE(size_t) * 8)] & (1ull << (idx % (K_SIZE(size_t) * 8))));
|
|
return empty;
|
|
}
|
|
|
|
constexpr Size HashToIndex(uint64_t hash) const
|
|
{
|
|
return (Size)(hash & (uint64_t)(N - 1));
|
|
}
|
|
};
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Date
|
|
// ------------------------------------------------------------------------
|
|
|
|
union LocalDate {
|
|
int32_t value;
|
|
struct {
|
|
#if defined(K_BIG_ENDIAN)
|
|
int16_t year;
|
|
int8_t month;
|
|
int8_t day;
|
|
#else
|
|
int8_t day;
|
|
int8_t month;
|
|
int16_t year;
|
|
#endif
|
|
} st;
|
|
|
|
LocalDate() = default;
|
|
#if defined(K_BIG_ENDIAN)
|
|
LocalDate(int16_t year, int8_t month, int8_t day)
|
|
: st({ year, month, day }) { K_ASSERT(IsValid()); }
|
|
#else
|
|
LocalDate(int16_t year, int8_t month, int8_t day)
|
|
: st({ day, month, year }) { K_ASSERT(IsValid()); }
|
|
#endif
|
|
|
|
static inline bool IsLeapYear(int16_t year)
|
|
{
|
|
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
|
|
}
|
|
static inline int8_t DaysInMonth(int16_t year, int8_t month)
|
|
{
|
|
static const int8_t DaysPerMonth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
|
return (int8_t)(DaysPerMonth[month - 1] + (month == 2 && IsLeapYear(year)));
|
|
}
|
|
|
|
static LocalDate FromJulianDays(int days);
|
|
static LocalDate FromCalendarDate(int days) { return LocalDate::FromJulianDays(days + 2440588); }
|
|
|
|
bool IsValid() const
|
|
{
|
|
if (st.year < -4712)
|
|
return false;
|
|
if (st.month < 1 || st.month > 12)
|
|
return false;
|
|
if (st.day < 1 || st.day > DaysInMonth(st.year, st.month))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool operator==(LocalDate other) const { return value == other.value; }
|
|
bool operator!=(LocalDate other) const { return value != other.value; }
|
|
bool operator>(LocalDate other) const { return value > other.value; }
|
|
bool operator>=(LocalDate other) const { return value >= other.value; }
|
|
bool operator<(LocalDate other) const { return value < other.value; }
|
|
bool operator<=(LocalDate other) const { return value <= other.value; }
|
|
|
|
int ToJulianDays() const;
|
|
int ToCalendarDate() const { return ToJulianDays() - 2440588; }
|
|
|
|
int GetWeekDay() const;
|
|
|
|
int operator-(LocalDate other) const
|
|
{ return ToJulianDays() - other.ToJulianDays(); }
|
|
|
|
LocalDate operator+(int days) const
|
|
{
|
|
if (days < 5 && days > -5) {
|
|
LocalDate date = *this;
|
|
if (days > 0) {
|
|
while (days--) {
|
|
++date;
|
|
}
|
|
} else {
|
|
while (days++) {
|
|
--date;
|
|
}
|
|
}
|
|
return date;
|
|
} else {
|
|
return LocalDate::FromJulianDays(ToJulianDays() + days);
|
|
}
|
|
}
|
|
// That'll fail with INT_MAX days but that's far more days than can
|
|
// be represented as a date anyway
|
|
LocalDate operator-(int days) const { return *this + (-days); }
|
|
|
|
LocalDate &operator+=(int days) { *this = *this + days; return *this; }
|
|
LocalDate &operator-=(int days) { *this = *this - days; return *this; }
|
|
LocalDate &operator++();
|
|
LocalDate operator++(int) { LocalDate date = *this; ++(*this); return date; }
|
|
LocalDate &operator--();
|
|
LocalDate operator--(int) { LocalDate date = *this; --(*this); return date; }
|
|
|
|
uint64_t Hash() const { return HashTraits<int32_t>::Hash(value); }
|
|
};
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Time
|
|
// ------------------------------------------------------------------------
|
|
|
|
int64_t GetUnixTime();
|
|
|
|
struct TimeSpec {
|
|
int16_t year;
|
|
int8_t month;
|
|
int8_t day;
|
|
int8_t week_day; // 1 (monday) to 7 (sunday)
|
|
|
|
int8_t hour;
|
|
int8_t min;
|
|
int8_t sec;
|
|
int16_t msec;
|
|
|
|
int16_t offset; // minutes
|
|
};
|
|
|
|
TimeSpec DecomposeTimeUTC(int64_t time);
|
|
TimeSpec DecomposeTimeLocal(int64_t time);
|
|
int64_t ComposeTimeUTC(const TimeSpec &spec);
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Clock
|
|
// ------------------------------------------------------------------------
|
|
|
|
#if defined(_MSC_VER) && !defined(_M_ARM64)
|
|
static inline int64_t GetCoreCycles()
|
|
{
|
|
return (int64_t)__rdtsc();
|
|
}
|
|
#elif defined(__i386__) || defined(__x86_64__)
|
|
static inline int64_t GetCoreCycles()
|
|
{
|
|
uint32_t counter_low, counter_high;
|
|
__asm__ __volatile__ ("cpuid; rdtsc"
|
|
: "=a" (counter_low), "=d" (counter_high)
|
|
: : "%ebx", "%ecx");
|
|
int64_t counter = ((int64_t)counter_high << 32) | counter_low;
|
|
return counter;
|
|
}
|
|
#elif defined(__aarch64__)
|
|
static inline int64_t GetCoreCycles()
|
|
{
|
|
uint64_t counter;
|
|
__asm__ __volatile__ ("mrs %0, cntvct_el0" : "=r" (counter));
|
|
return counter;
|
|
}
|
|
#endif
|
|
|
|
int64_t GetMonotonicClock();
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Format
|
|
// ------------------------------------------------------------------------
|
|
|
|
enum class FmtType {
|
|
Str,
|
|
PadStr,
|
|
RepeatStr,
|
|
Char,
|
|
Buffer,
|
|
Custom,
|
|
Bool,
|
|
Integer,
|
|
Unsigned,
|
|
Float,
|
|
Double,
|
|
Binary,
|
|
Octal,
|
|
BigHex,
|
|
SmallHex,
|
|
BigBytes,
|
|
SmallBytes,
|
|
MemorySize,
|
|
DiskSize,
|
|
Date,
|
|
TimeISO,
|
|
TimeNice,
|
|
List,
|
|
FlagNames,
|
|
FlagOptions,
|
|
Random,
|
|
SafeStr,
|
|
SafeChar
|
|
};
|
|
|
|
template <typename T>
|
|
class FmtTraits {
|
|
public:
|
|
static void Format(const T &obj, FunctionRef<void(Span<const char>)> append) { return obj.Format(append); }
|
|
};
|
|
|
|
class FmtCustom {
|
|
struct Base {
|
|
virtual void Format(FunctionRef<void(Span<const char>)> append) const = 0;
|
|
};
|
|
|
|
template <typename T>
|
|
struct Concrete: public Base {
|
|
typedef void FuncType(const T &obj, FunctionRef<void(Span<const char>)>);
|
|
|
|
const T *obj;
|
|
FuncType *func;
|
|
|
|
Concrete(const T *obj, FuncType *func) : obj(obj), func(func) {}
|
|
|
|
void Format(FunctionRef<void(Span<const char>)> append) const override { func(*obj, append); }
|
|
};
|
|
|
|
// Concrete has 3 pointers (vtable, obj, func), so size should match!
|
|
void *raw[3];
|
|
|
|
public:
|
|
FmtCustom() = default;
|
|
|
|
template <typename T>
|
|
explicit FmtCustom(const T &obj)
|
|
{
|
|
static_assert(K_SIZE(*this) <= K_SIZE(raw));
|
|
new (raw) Concrete<T>(&obj, &FmtTraits<T>::Format);
|
|
}
|
|
|
|
void Format(FunctionRef<void(Span<const char>)> append) const
|
|
{
|
|
const Base *ptr = reinterpret_cast<const Base *>(&raw);
|
|
ptr->Format(append);
|
|
}
|
|
};
|
|
|
|
class FmtArg {
|
|
public:
|
|
FmtType type;
|
|
union {
|
|
Span<const char> str;
|
|
struct {
|
|
const char *str;
|
|
int count;
|
|
} repeat;
|
|
char buf[32];
|
|
char ch;
|
|
FmtCustom custom;
|
|
bool b;
|
|
int64_t i;
|
|
uint64_t u;
|
|
Span<const uint8_t> hex;
|
|
struct {
|
|
float value;
|
|
int min_prec;
|
|
int max_prec;
|
|
} f;
|
|
struct {
|
|
double value;
|
|
int min_prec;
|
|
int max_prec;
|
|
} d;
|
|
const void *ptr;
|
|
LocalDate date;
|
|
struct {
|
|
TimeSpec spec;
|
|
bool ms;
|
|
} time;
|
|
struct {
|
|
Size len;
|
|
const char *chars;
|
|
} random;
|
|
struct {
|
|
uint64_t flags;
|
|
union {
|
|
Span<const char *const> names;
|
|
Span<const struct OptionDesc> options;
|
|
} u;
|
|
const char *separator;
|
|
} list;
|
|
} u;
|
|
|
|
int pad = 0;
|
|
char padding = 0;
|
|
|
|
FmtArg() = default;
|
|
FmtArg(const FmtArg &other) = default;
|
|
FmtArg(std::nullptr_t) : FmtArg(FmtType::Str) { u.str = "(null)"; }
|
|
FmtArg(const char *str) : FmtArg(FmtType::Str) { u.str = str ? str : "(null)"; }
|
|
FmtArg(Span<const char> str) : FmtArg(FmtType::Str) { u.str = str; }
|
|
FmtArg(char c) : FmtArg(FmtType::Char) { u.ch = c; }
|
|
FmtArg(const FmtCustom &custom) : FmtArg(FmtType::Custom) { u.custom = custom; }
|
|
FmtArg(bool b) : FmtArg(FmtType::Bool) { u.b = b; }
|
|
FmtArg(unsigned char i) : FmtArg(FmtType::Unsigned) { u.u = i; }
|
|
FmtArg(short i) : FmtArg(FmtType::Integer) { u.i = i; }
|
|
FmtArg(unsigned short i) : FmtArg(FmtType::Unsigned) { u.u = i; }
|
|
FmtArg(int i) : FmtArg(FmtType::Integer) { u.i = i; }
|
|
FmtArg(unsigned int i) : FmtArg(FmtType::Unsigned) { u.u = i; }
|
|
FmtArg(long i) : FmtArg(FmtType::Integer) { u.i = i; }
|
|
FmtArg(unsigned long i) : FmtArg(FmtType::Unsigned) { u.u = i; }
|
|
FmtArg(long long i) : FmtArg(FmtType::Integer) { u.i = i; }
|
|
FmtArg(unsigned long long i) : FmtArg(FmtType::Unsigned) { u.u = i; }
|
|
FmtArg(float f) : FmtArg(FmtType::Float) { u.f = { f, 0, INT_MAX }; }
|
|
FmtArg(double d) : FmtArg(FmtType::Double) { u.d = { d, 0, INT_MAX }; }
|
|
FmtArg(const void *ptr) : FmtArg(FmtType::BigHex) { u.u = (uint64_t)ptr; }
|
|
FmtArg(const LocalDate &date) : FmtArg(FmtType::Date) { u.date = date; }
|
|
|
|
protected:
|
|
FmtArg(FmtType type) : type(type) {}
|
|
};
|
|
|
|
class FmtSafe: public FmtArg {
|
|
public:
|
|
FmtSafe() = default;
|
|
FmtSafe(FmtArg arg) : FmtArg(arg) {}
|
|
FmtSafe(std::nullptr_t) : FmtArg(FmtType::Str) { u.str = "(null)"; }
|
|
FmtSafe(const char *str) : FmtArg(FmtType::SafeStr) { u.str = str ? str : "(null)"; } // safe
|
|
FmtSafe(Span<const char> str) : FmtArg(FmtType::SafeStr) { u.str = str; } // safe
|
|
FmtSafe(char c) : FmtArg(FmtType::SafeChar) { u.ch = c; } // safe
|
|
FmtSafe(const FmtCustom &custom) : FmtArg(custom) {}
|
|
FmtSafe(bool b) : FmtArg(b) {}
|
|
FmtSafe(unsigned char i) : FmtArg(i) {}
|
|
FmtSafe(short i) : FmtArg(i) {}
|
|
FmtSafe(unsigned short i) : FmtArg(i) {}
|
|
FmtSafe(int i) : FmtArg(i) {}
|
|
FmtSafe(unsigned int i) : FmtArg(i) {}
|
|
FmtSafe(long i) : FmtArg(i) {}
|
|
FmtSafe(unsigned long i) : FmtArg(i) {}
|
|
FmtSafe(long long i) : FmtArg(i) {}
|
|
FmtSafe(unsigned long long i) : FmtArg(i) {}
|
|
FmtSafe(float f) : FmtArg(f) {}
|
|
FmtSafe(double d) : FmtArg(d) {}
|
|
FmtSafe(const void *ptr) : FmtArg(ptr) {}
|
|
FmtSafe(const LocalDate &date) : FmtArg(date) {}
|
|
};
|
|
|
|
static inline FmtArg FmtInt(long long i, int pad = 0, char padding = '0')
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::Integer;
|
|
arg.u.i = i;
|
|
arg.pad = pad;
|
|
arg.padding = padding;
|
|
return arg;
|
|
}
|
|
static inline FmtArg FmtInt(unsigned long long u, int pad = 0, char padding = '0')
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::Unsigned;
|
|
arg.u.u = u;
|
|
arg.pad = pad;
|
|
arg.padding = padding;
|
|
return arg;
|
|
}
|
|
static inline FmtArg FmtInt(unsigned char u, int pad = 0, char padding = '0') { return FmtInt((unsigned long long)u, pad, padding); }
|
|
static inline FmtArg FmtInt(short i, int pad = 0, char padding = '0') { return FmtInt((long long)i, pad, padding); }
|
|
static inline FmtArg FmtInt(unsigned short u, int pad = 0, char padding = '0') { return FmtInt((unsigned long long)u, pad, padding); }
|
|
static inline FmtArg FmtInt(int i, int pad = 0, char padding = '0') { return FmtInt((long long)i, pad, padding); }
|
|
static inline FmtArg FmtInt(unsigned int u, int pad = 0, char padding = '0') { return FmtInt((unsigned long long)u, pad, padding); }
|
|
static inline FmtArg FmtInt(long i, int pad = 0, char padding = '0') { return FmtInt((long long)i, pad, padding); }
|
|
static inline FmtArg FmtInt(unsigned long u, int pad = 0, char padding = '0') { return FmtInt((unsigned long long)u, pad, padding); }
|
|
|
|
static inline FmtArg FmtBin(uint64_t u, int pad = 0, char padding = '0')
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::Binary;
|
|
arg.u.u = u;
|
|
arg.pad = pad;
|
|
arg.padding = padding;
|
|
return arg;
|
|
}
|
|
static inline FmtArg FmtOctal(uint64_t u, int pad = 0, char padding = '0')
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::Octal;
|
|
arg.u.u = u;
|
|
arg.pad = pad;
|
|
arg.padding = padding;
|
|
return arg;
|
|
}
|
|
static inline FmtArg FmtHex(uint64_t u, int pad = 0, char padding = '0')
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::BigHex;
|
|
arg.u.u = u;
|
|
arg.pad = pad;
|
|
arg.padding = padding;
|
|
return arg;
|
|
}
|
|
static inline FmtArg FmtHexSmall(uint64_t u, int pad = 0, char padding = '0')
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::SmallHex;
|
|
arg.u.u = u;
|
|
arg.pad = pad;
|
|
arg.padding = padding;
|
|
return arg;
|
|
}
|
|
|
|
static inline FmtArg FmtFloat(float f, int min_prec, int max_prec)
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::Float;
|
|
arg.u.f.value = f;
|
|
arg.u.f.min_prec = min_prec;
|
|
arg.u.f.max_prec = max_prec;
|
|
return arg;
|
|
}
|
|
static inline FmtArg FmtFloat(float f, int prec) { return FmtFloat(f, prec, prec); }
|
|
static inline FmtArg FmtFloat(float f) { return FmtFloat(f, 0, INT_MAX); }
|
|
|
|
static inline FmtArg FmtDouble(double d, int min_prec, int max_prec)
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::Double;
|
|
arg.u.d.value = d;
|
|
arg.u.d.min_prec = min_prec;
|
|
arg.u.d.max_prec = max_prec;
|
|
return arg;
|
|
}
|
|
static inline FmtArg FmtDouble(double d, int prec) { return FmtDouble(d, prec, prec); }
|
|
static inline FmtArg FmtDouble(double d) { return FmtDouble(d, 0, INT_MAX); }
|
|
|
|
static inline FmtArg FmtMemSize(int64_t size)
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::MemorySize;
|
|
arg.u.i = size;
|
|
return arg;
|
|
}
|
|
static inline FmtArg FmtDiskSize(int64_t size)
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::DiskSize;
|
|
arg.u.i = size;
|
|
return arg;
|
|
}
|
|
|
|
static inline FmtArg FmtTimeISO(TimeSpec spec, bool ms = false)
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::TimeISO;
|
|
arg.u.time.spec = spec;
|
|
arg.u.time.ms = ms;
|
|
return arg;
|
|
}
|
|
|
|
static inline FmtArg FmtTimeNice(TimeSpec spec, bool ms = false)
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::TimeNice;
|
|
arg.u.time.spec = spec;
|
|
arg.u.time.ms = ms;
|
|
return arg;
|
|
}
|
|
|
|
static inline FmtArg FmtList(Span<const char *const> names, const char *sep = ", ")
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::List;
|
|
arg.u.list.u.names = names;
|
|
arg.u.list.separator = sep;
|
|
return arg;
|
|
}
|
|
static inline FmtArg FmtFlags(uint64_t flags, Span<const char *const> names, const char *sep = ", ")
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::FlagNames;
|
|
arg.u.list.flags = flags & ((1ull << names.len) - 1);
|
|
arg.u.list.u.names = names;
|
|
arg.u.list.separator = sep;
|
|
return arg;
|
|
}
|
|
static inline FmtArg FmtFlags(uint64_t flags, Span<const struct OptionDesc> options, const char *sep = ", ")
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::FlagOptions;
|
|
arg.u.list.flags = flags & ((1ull << options.len) - 1);
|
|
arg.u.list.u.options = options;
|
|
arg.u.list.separator = sep;
|
|
return arg;
|
|
}
|
|
|
|
static inline FmtArg FmtPad(Span<const char> str, int pad, char padding = ' ')
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::PadStr;
|
|
arg.u.str = str;
|
|
arg.pad = pad;
|
|
arg.padding = padding;
|
|
return arg;
|
|
}
|
|
static inline FmtArg FmtRepeat(const char *str, int count)
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::RepeatStr;
|
|
arg.u.repeat.str = str;
|
|
arg.u.repeat.count = count;
|
|
return arg;
|
|
}
|
|
|
|
static inline FmtArg FmtHex(Span<const uint8_t> buf)
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::BigBytes;
|
|
arg.u.hex = buf;
|
|
return arg;
|
|
}
|
|
static inline FmtArg FmtHexSmall(Span<const uint8_t> buf)
|
|
{
|
|
FmtArg arg;
|
|
arg.type = FmtType::SmallBytes;
|
|
arg.u.hex = buf;
|
|
return arg;
|
|
}
|
|
|
|
static inline FmtArg FmtRandom(Size len, const char *chars = nullptr)
|
|
{
|
|
K_ASSERT(len < 256);
|
|
len = std::min(len, (Size)256);
|
|
|
|
FmtArg arg;
|
|
arg.type = FmtType::Random;
|
|
arg.u.random.len = len;
|
|
arg.u.random.chars = chars;
|
|
return arg;
|
|
}
|
|
|
|
class FmtUpperAscii {
|
|
Span<const char> str;
|
|
|
|
public:
|
|
FmtUpperAscii(Span<const char> str) : str(str) {}
|
|
|
|
void Format(FunctionRef<void(Span<const char>)> append) const;
|
|
operator FmtArg() const { return FmtCustom(*this); }
|
|
};
|
|
|
|
class FmtLowerAscii {
|
|
Span<const char> str;
|
|
|
|
public:
|
|
FmtLowerAscii(Span<const char> str) : str(str) {}
|
|
|
|
void Format(FunctionRef<void(Span<const char>)> append) const;
|
|
operator FmtArg() const { return FmtCustom(*this); }
|
|
};
|
|
|
|
class FmtUrlSafe {
|
|
Span<const char> str;
|
|
const char *passthrough;
|
|
|
|
public:
|
|
FmtUrlSafe(Span<const char> str, const char *passthrough)
|
|
: str(str), passthrough(passthrough) {}
|
|
|
|
void Format(FunctionRef<void(Span<const char>)> append) const;
|
|
operator FmtArg() const { return FmtCustom(*this); }
|
|
};
|
|
|
|
class FmtHtmlSafe {
|
|
Span<const char> str;
|
|
|
|
public:
|
|
FmtHtmlSafe(Span<const char> str) : str(str) {}
|
|
|
|
void Format(FunctionRef<void(Span<const char>)> append) const;
|
|
operator FmtArg() const { return FmtCustom(*this); }
|
|
};
|
|
|
|
class FmtEscape {
|
|
Span<const char> str;
|
|
char quote;
|
|
|
|
public:
|
|
FmtEscape(Span<const char> str, char quote) : str(str), quote(quote) {}
|
|
|
|
void Format(FunctionRef<void(Span<const char>)> append) const;
|
|
operator FmtArg() const { return FmtCustom(*this); }
|
|
};
|
|
|
|
FmtArg FmtVersion(int64_t version, int parts, int by);
|
|
|
|
enum class LogLevel {
|
|
Debug,
|
|
Info,
|
|
Warning,
|
|
Error
|
|
};
|
|
|
|
Span<char> FmtFmt(const char *fmt, Span<const FmtArg> args, bool vt100, Span<char> out_buf);
|
|
Span<char> FmtFmt(const char *fmt, Span<const FmtArg> args, bool vt100, HeapArray<char> *out_buf);
|
|
Span<char> FmtFmt(const char *fmt, Span<const FmtArg> args, bool vt100, Allocator *alloc);
|
|
void FmtFmt(const char *fmt, Span<const FmtArg> args, bool vt100, FunctionRef<void(Span<const char>)> append);
|
|
void PrintFmt(const char *fmt, Span<const FmtArg> args, StreamWriter *out_st);
|
|
void PrintLnFmt(const char *fmt, Span<const FmtArg> args, StreamWriter *out_st);
|
|
|
|
#define DEFINE_FMT_VARIANT(Ret, Type) \
|
|
static inline Ret Fmt(Type out, const char *fmt) \
|
|
{ \
|
|
return FmtFmt(fmt, {}, false, out); \
|
|
} \
|
|
static inline Ret Fmt(Type out, bool vt100, const char *fmt) \
|
|
{ \
|
|
return FmtFmt(fmt, {}, vt100, out); \
|
|
} \
|
|
template <typename... Args> \
|
|
Ret Fmt(Type out, const char *fmt, Args... args) \
|
|
{ \
|
|
const FmtArg fmt_args[] = { FmtArg(args)... }; \
|
|
return FmtFmt(fmt, fmt_args, false, out); \
|
|
} \
|
|
template <typename... Args> \
|
|
Ret Fmt(Type out, bool vt100, const char *fmt, Args... args) \
|
|
{ \
|
|
const FmtArg fmt_args[] = { FmtArg(args)... }; \
|
|
return FmtFmt(fmt, fmt_args, vt100, out); \
|
|
}
|
|
#define DEFINE_PRINT_VARIANT(Name, Ret, Type) \
|
|
static inline Ret Name(Type out, const char *fmt) \
|
|
{ \
|
|
return Name##Fmt(fmt, {}, out); \
|
|
} \
|
|
template <typename... Args> \
|
|
Ret Name(Type out, const char *fmt, Args... args) \
|
|
{ \
|
|
const FmtArg fmt_args[] = { FmtArg(args)... }; \
|
|
return Name##Fmt(fmt, fmt_args, out); \
|
|
}
|
|
|
|
DEFINE_FMT_VARIANT(Span<char>, Span<char>)
|
|
DEFINE_FMT_VARIANT(Span<char>, HeapArray<char> *)
|
|
DEFINE_FMT_VARIANT(Span<char>, Allocator *)
|
|
DEFINE_FMT_VARIANT(void, FunctionRef<void(Span<const char>)>)
|
|
DEFINE_PRINT_VARIANT(Print, void, StreamWriter *)
|
|
DEFINE_PRINT_VARIANT(PrintLn, void, StreamWriter *)
|
|
|
|
#undef DEFINE_FMT_VARIANT
|
|
#undef DEFINE_PRINT_VARIANT
|
|
|
|
// Print formatted strings to stdout
|
|
template <typename... Args>
|
|
void Print(const char *fmt, Args... args)
|
|
{
|
|
Print(StdOut, fmt, args...);
|
|
}
|
|
template <typename... Args>
|
|
void PrintLn(const char *fmt, Args... args)
|
|
{
|
|
PrintLn(StdOut, fmt, args...);
|
|
}
|
|
|
|
// PrintLn variants without format strings
|
|
void PrintLn(StreamWriter *out_st);
|
|
void PrintLn();
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Debug and errors
|
|
// ------------------------------------------------------------------------
|
|
|
|
typedef void LogFunc(LogLevel level, const char *ctx, const char *msg);
|
|
typedef void LogFilterFunc(LogLevel level, const char *ctx, const char *msg,
|
|
FunctionRef<LogFunc> func);
|
|
|
|
const char *GetEnv(const char *name);
|
|
bool GetDebugFlag(const char *name);
|
|
|
|
void LogFmt(LogLevel level, const char *ctx, const char *fmt, Span<const FmtArg> args);
|
|
|
|
static inline void Log(LogLevel level, const char *ctx)
|
|
{
|
|
LogFmt(level, ctx, "", {});
|
|
}
|
|
static inline void Log(LogLevel level, const char *ctx, const char *fmt)
|
|
{
|
|
LogFmt(level, ctx, fmt, {});
|
|
}
|
|
template <typename... Args>
|
|
static inline void Log(LogLevel level, const char *ctx, const char *fmt, Args... args)
|
|
{
|
|
const FmtArg fmt_args[] = { FmtSafe(args)... };
|
|
LogFmt(level, ctx, fmt, fmt_args);
|
|
}
|
|
|
|
#if defined(K_DEBUG) && __cplusplus >= 202002L && __has_include(<source_location>)
|
|
struct LogContext {
|
|
const char *fmt;
|
|
char str[56] = {};
|
|
|
|
consteval LogContext(const char *fmt, const std::source_location &location = std::source_location::current())
|
|
: fmt(fmt)
|
|
{
|
|
Span<const char> filename = location.file_name();
|
|
int line = (int)location.line();
|
|
|
|
int digits = 1 + (line >= 10) + (line >= 100) + (line >= 1000) +
|
|
(line >= 10000) + (line >= 100000) + (line >= 1000000) +
|
|
(line >= 10000000) + (line >= 100000000) + (line >= 1000000000);
|
|
|
|
// Cut long filenames to make sure is 32 characters long at most
|
|
Size treshold = 25 - digits;
|
|
Size offset = 0;
|
|
|
|
str[offset++] = '[';
|
|
|
|
if (filename.len > treshold) {
|
|
filename = filename.Take(filename.len - treshold, treshold);
|
|
|
|
str[offset++] = '.';
|
|
str[offset++] = '.';
|
|
str[offset++] = '.';
|
|
}
|
|
|
|
for (char c: filename) {
|
|
str[offset++] = c;
|
|
}
|
|
str[offset++] = ':';
|
|
|
|
offset += digits;
|
|
for (int i = 0; i < digits; i++) {
|
|
str[offset - i - 1] = '0' + (line % 10);
|
|
line /= 10;
|
|
}
|
|
|
|
str[offset++] = ']';
|
|
str[offset++] = ' ';
|
|
}
|
|
};
|
|
|
|
template <typename... Args>
|
|
static inline void LogDebug(LogContext ctx, Args... args) { Log(LogLevel::Debug, ctx.str, ctx.fmt, args...); }
|
|
template <typename... Args>
|
|
static inline void LogInfo() { Log(LogLevel::Info, nullptr); }
|
|
template <typename... Args>
|
|
static inline void LogInfo(const char *fmt, Args... args) { Log(LogLevel::Info, nullptr, fmt, args...); }
|
|
template <typename... Args>
|
|
static inline void LogWarning(LogContext ctx, Args... args) { Log(LogLevel::Warning, ctx.str, ctx.fmt, args...); }
|
|
template <typename... Args>
|
|
static inline void LogError(LogContext ctx, Args... args) { Log(LogLevel::Error, ctx.str, ctx.fmt, args...); }
|
|
#else
|
|
#if defined(K_DEBUG)
|
|
template <typename... Args>
|
|
static inline void LogDebug(Args... args) { Log(LogLevel::Debug, "Debug: ", args...); }
|
|
#else
|
|
template <typename... Args>
|
|
static inline void LogDebug(Args...) {}
|
|
#endif
|
|
template <typename... Args>
|
|
static inline void LogInfo(Args... args) { Log(LogLevel::Info, nullptr, args...); }
|
|
template <typename... Args>
|
|
static inline void LogWarning(Args... args) { Log(LogLevel::Warning, T("Warning: "), args...); }
|
|
template <typename... Args>
|
|
static inline void LogError(Args... args) { Log(LogLevel::Error, T("Error: "), args...); }
|
|
#endif
|
|
|
|
void SetLogHandler(const std::function<LogFunc> &func, bool vt100);
|
|
void DefaultLogHandler(LogLevel level, const char *ctx, const char *msg);
|
|
|
|
void PushLogFilter(const std::function<LogFilterFunc> &func);
|
|
void PopLogFilter();
|
|
|
|
#if defined(_WIN32)
|
|
bool RedirectLogToWindowsEvents(const char *name);
|
|
#endif
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Progress
|
|
// ------------------------------------------------------------------------
|
|
|
|
#if !defined(__wasi__)
|
|
|
|
struct ProgressNode;
|
|
|
|
struct ProgressInfo {
|
|
Span<const char> text;
|
|
|
|
bool determinate;
|
|
int64_t value;
|
|
int64_t min;
|
|
int64_t max;
|
|
};
|
|
|
|
typedef void ProgressFunc(Span<const ProgressInfo> states);
|
|
|
|
class ProgressHandle {
|
|
char text[K_PROGRESS_TEXT_SIZE] = {};
|
|
|
|
std::atomic<ProgressNode *> node = nullptr;
|
|
|
|
public:
|
|
ProgressHandle() {}
|
|
ProgressHandle(Span<const char> str) { CopyText(str, text); }
|
|
~ProgressHandle();
|
|
|
|
void Set(int64_t value, int64_t min, int64_t max);
|
|
void Set(int64_t value, int64_t max) { Set(value, 0, max); }
|
|
void Set() { Set(0, 0, 0); }
|
|
|
|
void Set(int64_t value, int64_t min, int64_t max, Span<const char> text);
|
|
void Set(int64_t value, int64_t max, Span<const char> text) { Set(value, 0, max, text); }
|
|
void Set(Span<const char> text) { Set(0, 0, 0, text); }
|
|
|
|
template<typename... Args>
|
|
void SetFmt(int64_t value, int64_t min, int64_t max, const char *fmt, Args... args)
|
|
{
|
|
char buf[K_PROGRESS_TEXT_SIZE];
|
|
Fmt(buf, fmt, args...);
|
|
|
|
Set(value, min, max, (const char *)buf);
|
|
}
|
|
template<typename... Args>
|
|
void SetFmt(int64_t value, int64_t max, const char *fmt, Args... args) { SetFmt(value, 0, max, fmt, args...); }
|
|
template<typename... Args>
|
|
void SetFmt(const char *fmt, Args... args) { SetFmt(1ll, 0ll, fmt, args...); }
|
|
|
|
template<typename... Args>
|
|
|
|
void operator()(int64_t value, int64_t min, int64_t max) { Set(value, min, max); }
|
|
void operator()(int64_t value, int64_t max) { Set(value, 0, max); }
|
|
void operator()() { Set(0, 0); }
|
|
|
|
private:
|
|
ProgressNode *AcquireNode();
|
|
void CopyText(Span<const char> text, char out[K_PROGRESS_TEXT_SIZE]);
|
|
};
|
|
|
|
void SetProgressHandler(const std::function<ProgressFunc> &func);
|
|
void DefaultProgressHandler(Span<const ProgressInfo> bars);
|
|
|
|
#endif
|
|
|
|
// ------------------------------------------------------------------------
|
|
// System
|
|
// ------------------------------------------------------------------------
|
|
|
|
#if defined(_WIN32)
|
|
#define K_PATH_SEPARATORS "\\/"
|
|
#define K_PATH_DELIMITER ';'
|
|
#define K_EXECUTABLE_EXTENSION ".exe"
|
|
#define K_SHARED_LIBRARY_EXTENSION ".dll"
|
|
#else
|
|
#define K_PATH_SEPARATORS "/"
|
|
#define K_PATH_DELIMITER ':'
|
|
#define K_EXECUTABLE_EXTENSION ""
|
|
#define K_SHARED_LIBRARY_EXTENSION ".so"
|
|
#endif
|
|
|
|
#if defined(_WIN32)
|
|
bool IsWin32Utf8();
|
|
Size ConvertUtf8ToWin32Wide(Span<const char> str, Span<wchar_t> out_str_w);
|
|
Size ConvertWin32WideToUtf8(const wchar_t *str_w, Span<char> out_str);
|
|
char *GetWin32ErrorString(uint32_t error_code = UINT32_MAX);
|
|
#endif
|
|
|
|
static inline bool IsPathSeparator(char c)
|
|
{
|
|
#if defined(_WIN32)
|
|
return c == '/' || c == '\\';
|
|
#else
|
|
return c == '/';
|
|
#endif
|
|
}
|
|
|
|
enum class CompressionType {
|
|
None,
|
|
Zlib,
|
|
Gzip,
|
|
Brotli,
|
|
LZ4,
|
|
Zstd
|
|
};
|
|
static const char *const CompressionTypeNames[] = {
|
|
"None",
|
|
"Zlib",
|
|
"Gzip",
|
|
"Brotli",
|
|
"LZ4",
|
|
"Zstd"
|
|
};
|
|
static const char *const CompressionTypeExtensions[] = {
|
|
nullptr,
|
|
".zz",
|
|
".gz",
|
|
".br",
|
|
".lz4",
|
|
".zst"
|
|
};
|
|
|
|
Span<const char> GetPathDirectory(Span<const char> filename);
|
|
Span<const char> GetPathExtension(Span<const char> filename,
|
|
CompressionType *out_compression_type = nullptr);
|
|
|
|
enum class NormalizeFlag {
|
|
EndWithSeparator = 1 << 0,
|
|
ForceSlash = 1 << 1,
|
|
NoExpansion = 1 << 2
|
|
};
|
|
|
|
Span<char> NormalizePath(Span<const char> path, Span<const char> root_directory, unsigned int flags, Allocator *alloc);
|
|
static inline Span<char> NormalizePath(Span<const char> path, Span<const char> root_directory, Allocator *alloc)
|
|
{ return NormalizePath(path, root_directory, 0, alloc); }
|
|
static inline Span<char> NormalizePath(Span<const char> path, unsigned int flags, Allocator *alloc)
|
|
{ return NormalizePath(path, {}, flags, alloc); }
|
|
static inline Span<char> NormalizePath(Span<const char> path, Allocator *alloc)
|
|
{ return NormalizePath(path, {}, 0, alloc); }
|
|
|
|
bool PathIsAbsolute(const char *path);
|
|
bool PathIsAbsolute(Span<const char> path);
|
|
bool PathContainsDotDot(const char *path);
|
|
bool PathContainsDotDot(Span<const char> path);
|
|
|
|
enum class StatFlag {
|
|
SilentMissing = 1 << 0,
|
|
FollowSymlink = 1 << 1
|
|
};
|
|
|
|
enum class FileType {
|
|
Directory,
|
|
File,
|
|
Link,
|
|
Device,
|
|
Pipe,
|
|
Socket
|
|
};
|
|
static const char *const FileTypeNames[] = {
|
|
"Directory",
|
|
"File",
|
|
"Link",
|
|
"Device",
|
|
"Pipe",
|
|
"Socket"
|
|
};
|
|
|
|
struct FileInfo {
|
|
FileType type;
|
|
|
|
int64_t size;
|
|
int64_t mtime;
|
|
int64_t ctime;
|
|
int64_t atime;
|
|
int64_t btime;
|
|
unsigned int mode;
|
|
|
|
uint32_t uid;
|
|
uint32_t gid;
|
|
};
|
|
|
|
enum class StatResult {
|
|
Success,
|
|
|
|
MissingPath,
|
|
AccessDenied,
|
|
OtherError
|
|
};
|
|
|
|
StatResult StatFile(int fd, const char *filename, unsigned int flags, FileInfo *out_info);
|
|
|
|
static inline StatResult StatFile(int fd, const char *filename, FileInfo *out_info)
|
|
{ return StatFile(fd, filename, 0, out_info); }
|
|
static inline StatResult StatFile(const char *filename, unsigned int flags, FileInfo *out_info)
|
|
{ return StatFile(-1, filename, flags, out_info); }
|
|
static inline StatResult StatFile(const char *filename, FileInfo *out_info)
|
|
{ return StatFile(-1, filename, 0, out_info); }
|
|
|
|
enum class RenameFlag {
|
|
Overwrite = 1 << 0,
|
|
Sync = 1 << 1
|
|
};
|
|
|
|
enum class RenameResult {
|
|
Success = 0,
|
|
|
|
AlreadyExists = 1 << 0,
|
|
OtherError = 1 << 1
|
|
};
|
|
|
|
// Sync failures are logged but not reported as errors
|
|
RenameResult RenameFile(const char *src_filename, const char *dest_filename, unsigned int silent, unsigned int flags);
|
|
static inline RenameResult RenameFile(const char *src_filename, const char *dest_filename, unsigned int flags)
|
|
{ return RenameFile(src_filename, dest_filename, 0, flags); }
|
|
|
|
bool ResizeFile(int fd, const char *filename, int64_t len);
|
|
|
|
#if !defined(_WIN32)
|
|
bool SetFileMode(int fd, const char *filename, uint32_t mode);
|
|
static inline bool SetFileMode(const char *filename, uint32_t mode)
|
|
{ return SetFileMode(-1, filename, mode); }
|
|
|
|
bool SetFileOwner(int fd, const char *filename, uint32_t uid, uint32_t gid);
|
|
static inline bool SetFileOwner(const char *filename, uint32_t uid, uint32_t gid)
|
|
{ return SetFileOwner(-1, filename, uid, gid); }
|
|
#endif
|
|
|
|
bool SetFileTimes(int fd, const char *filename, int64_t mtime, int64_t ctime);
|
|
static inline bool SetFileTimes(const char *filename, int64_t mtime, int64_t ctime)
|
|
{ return SetFileTimes(-1, filename, mtime, ctime); }
|
|
|
|
struct VolumeInfo {
|
|
int64_t total;
|
|
int64_t available;
|
|
};
|
|
|
|
#if !defined(__wasm__)
|
|
bool GetVolumeInfo(const char *dirname, VolumeInfo *out_volume);
|
|
#endif
|
|
|
|
enum class EnumResult {
|
|
Success,
|
|
|
|
MissingPath,
|
|
AccessDenied,
|
|
PartialEnum,
|
|
CallbackFail,
|
|
OtherError
|
|
};
|
|
|
|
EnumResult EnumerateDirectory(const char *dirname, const char *filter, Size max_files,
|
|
FunctionRef<bool(const char *, FileType)> func);
|
|
EnumResult EnumerateDirectory(const char *dirname, const char *filter, Size max_files,
|
|
FunctionRef<bool(const char *, const FileInfo &)> func);
|
|
|
|
#if !defined(_WIN32) && !defined(__APPLE__)
|
|
EnumResult EnumerateDirectory(int fd, const char *dirname, const char *filter, Size max_files,
|
|
FunctionRef<bool(const char *, FileType)> func);
|
|
EnumResult EnumerateDirectory(int fd, const char *dirname, const char *filter, Size max_files,
|
|
FunctionRef<bool(const char *, const FileInfo &)> func);
|
|
#endif
|
|
|
|
bool EnumerateFiles(const char *dirname, const char *filter, Size max_depth, Size max_files,
|
|
Allocator *str_alloc, HeapArray<const char *> *out_files);
|
|
bool IsDirectoryEmpty(const char *dirname);
|
|
|
|
bool TestFile(const char *filename);
|
|
bool TestFile(const char *filename, FileType type);
|
|
bool IsDirectory(const char *filename);
|
|
|
|
#if defined(_WIN32)
|
|
bool MatchPathName(const char *path, const char *spec, bool case_sensitive = false);
|
|
bool MatchPathSpec(const char *path, const char *spec, bool case_sensitive = false);
|
|
#else
|
|
bool MatchPathName(const char *path, const char *spec, bool case_sensitive = true);
|
|
bool MatchPathSpec(const char *path, const char *spec, bool case_sensitive = true);
|
|
#endif
|
|
|
|
bool FindExecutableInPath(const char *path, const char *name,
|
|
Allocator *alloc = nullptr, const char **out_path = nullptr);
|
|
bool FindExecutableInPath(const char *name, Allocator *alloc = nullptr,
|
|
const char **out_path = nullptr);
|
|
|
|
bool SetWorkingDirectory(const char *directory);
|
|
const char *GetWorkingDirectory();
|
|
|
|
const char *GetApplicationExecutable(); // Can be NULL (EmSDK)
|
|
const char *GetApplicationDirectory(); // Can be NULL (EmSDK)
|
|
|
|
bool MakeDirectory(const char *directory, bool error_if_exists = true);
|
|
bool MakeDirectoryRec(Span<const char> directory);
|
|
bool UnlinkDirectory(const char *directory, bool error_if_missing = false);
|
|
bool UnlinkFile(const char *filename, bool error_if_missing = false);
|
|
bool EnsureDirectoryExists(const char *filename);
|
|
|
|
enum class OpenFlag {
|
|
Read = 1 << 0,
|
|
Write = 1 << 1,
|
|
Append = 1 << 2,
|
|
|
|
Keep = 1 << 3,
|
|
Exists = 1 << 4,
|
|
Exclusive = 1 << 5,
|
|
|
|
NoFollow = 1 << 6,
|
|
Directory = 1 << 7
|
|
};
|
|
|
|
enum class OpenResult {
|
|
Success = 0,
|
|
|
|
MissingPath = 1 << 0,
|
|
FileExists = 1 << 1,
|
|
AccessDenied = 1 << 2,
|
|
OtherError = 1 << 3
|
|
};
|
|
|
|
OpenResult OpenFile(const char *filename, unsigned int flags, unsigned int silent, int *out_fd);
|
|
|
|
static inline OpenResult OpenFile(const char *filename, unsigned int flags, int *out_fd)
|
|
{ return OpenFile(filename, flags, 0, out_fd); }
|
|
static inline int OpenFile(const char *filename, unsigned int flags)
|
|
{
|
|
int fd = -1;
|
|
if (OpenFile(filename, flags, &fd) != OpenResult::Success)
|
|
return -1;
|
|
return fd;
|
|
}
|
|
|
|
void CloseDescriptor(int fd);
|
|
bool FlushFile(int fd, const char *filename);
|
|
|
|
bool SpliceFile(int src_fd, const char *src_filename, int64_t src_offset,
|
|
int dest_fd, const char *dest_filename, int64_t dest_offset, int64_t size,
|
|
FunctionRef<void(int64_t, int64_t)> progress = [](int64_t, int64_t) {});
|
|
static inline bool SpliceFile(int src_fd, const char *src_filename, int dest_fd, const char *dest_filename, int64_t size,
|
|
FunctionRef<void(int64_t, int64_t)> progress = [](int64_t, int64_t) {})
|
|
{ return SpliceFile(src_fd, src_filename, 0, dest_fd, dest_filename, 0, size, progress); }
|
|
|
|
bool FileIsVt100(int fd);
|
|
|
|
#if !defined(__wasi__)
|
|
|
|
#if defined(_WIN32)
|
|
enum class PipeMode {
|
|
Byte,
|
|
Message
|
|
};
|
|
|
|
bool CreateOverlappedPipe(bool overlap0, bool overlap1, PipeMode mode, void *out_handles[2]); // HANDLE
|
|
void CloseHandleSafe(void **handle_ptr); // HANDLE
|
|
#else
|
|
void SetSignalHandler(int signal, void (*func)(int), struct sigaction *prev = nullptr);
|
|
|
|
bool CreatePipe(bool block, int out_pfd[2]);
|
|
void CloseDescriptorSafe(int *fd_ptr);
|
|
#endif
|
|
|
|
struct ExecuteInfo {
|
|
struct KeyValue {
|
|
const char *key;
|
|
const char *value;
|
|
};
|
|
|
|
const char *work_dir = nullptr;
|
|
|
|
bool reset_env = false;
|
|
Span<const KeyValue> env_variables = {};
|
|
};
|
|
|
|
bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
|
|
FunctionRef<Span<const uint8_t>()> in_func,
|
|
FunctionRef<void(Span<uint8_t> buf)> out_func, int *out_code);
|
|
bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
|
|
Span<const uint8_t> in_buf, Size max_len,
|
|
HeapArray<uint8_t> *out_buf, int *out_code);
|
|
|
|
// Simple variants
|
|
static inline bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info, int *out_code)
|
|
{ return ExecuteCommandLine(cmd_line, info, {}, {}, out_code); }
|
|
static inline bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
|
|
Span<const uint8_t> in_buf,
|
|
FunctionRef<void(Span<uint8_t> buf)> out_func, int *out_code)
|
|
{
|
|
const auto read_once = [&]() {
|
|
Span<const uint8_t> buf = in_buf;
|
|
in_buf = {};
|
|
return buf;
|
|
};
|
|
|
|
return ExecuteCommandLine(cmd_line, info, read_once, out_func, out_code);
|
|
}
|
|
|
|
// Char variants
|
|
static inline bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
|
|
Span<const char> in_buf,
|
|
FunctionRef<void(Span<char> buf)> out_func, int *out_code)
|
|
{
|
|
const auto write = [&](Span<uint8_t> buf) { out_func(buf.As<char>()); };
|
|
return ExecuteCommandLine(cmd_line, info, in_buf.As<const uint8_t>(), write, out_code);
|
|
}
|
|
static inline bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
|
|
Span<const char> in_buf, Size max_len,
|
|
HeapArray<char> *out_buf, int *out_code)
|
|
{
|
|
return ExecuteCommandLine(cmd_line, info, in_buf.As<const uint8_t>(), max_len,
|
|
(HeapArray<uint8_t> *)out_buf, out_code);
|
|
}
|
|
|
|
Size ReadCommandOutput(const char *cmd_line, Span<char> out_output);
|
|
bool ReadCommandOutput(const char *cmd_line, HeapArray<char> *out_output);
|
|
|
|
#endif
|
|
|
|
void WaitDelay(int64_t delay);
|
|
|
|
#if !defined(__wasi__)
|
|
|
|
enum class WaitResult {
|
|
Ready,
|
|
Timeout,
|
|
Interrupt,
|
|
Message,
|
|
Exit
|
|
};
|
|
|
|
struct WaitSource {
|
|
#if defined(_WIN32)
|
|
// Special-cased on Windows: set to NULL to wait for the Win32 message pump too
|
|
void *handle; // HANDLE
|
|
int timeout;
|
|
#else
|
|
int fd;
|
|
int timeout;
|
|
int events = 0;
|
|
#endif
|
|
};
|
|
|
|
// After WaitEvents() has been called once (even with timeout 0), a few signals (such as SIGINT, SIGHUP)
|
|
// and their Windows equivalent will be permanently ignored.
|
|
// Only the main thread (running main) will get WaitResult::Message events (and SIGUSR1).
|
|
WaitResult WaitEvents(Span<const WaitSource> sources, int64_t timeout, uint64_t *out_ready = nullptr);
|
|
WaitResult WaitEvents(int64_t timeout);
|
|
|
|
void PostWaitMessage();
|
|
void PostTerminate();
|
|
|
|
#endif
|
|
|
|
int GetCoreCount();
|
|
|
|
#if !defined(_WIN32) && !defined(__wasi__)
|
|
bool RaiseMaximumOpenFiles(int limit = -1);
|
|
bool DropRootIdentity();
|
|
#endif
|
|
|
|
#if defined(__linux__)
|
|
bool NotifySystemd();
|
|
#endif
|
|
|
|
#define K_RESTART_EINTR(CallCode, ErrorCond) \
|
|
([&]() { \
|
|
decltype(CallCode) ret; \
|
|
while ((ret = (CallCode)) ErrorCond && errno == EINTR); \
|
|
return ret; \
|
|
})()
|
|
|
|
class InitHelper {
|
|
public:
|
|
const char *name;
|
|
InitHelper *next = nullptr;
|
|
|
|
InitHelper(const char *name);
|
|
virtual void Run() = 0;
|
|
};
|
|
|
|
class FinalizeHelper {
|
|
public:
|
|
const char *name;
|
|
FinalizeHelper *next = nullptr;
|
|
|
|
FinalizeHelper(const char *name);
|
|
virtual void Run() = 0;
|
|
};
|
|
|
|
#define K_INIT_(ClassName, Name) \
|
|
class ClassName: public InitHelper { \
|
|
public: \
|
|
ClassName(): InitHelper(Name) {} \
|
|
void Run() override; \
|
|
}; \
|
|
static ClassName K_UNIQUE_NAME(init); \
|
|
void ClassName::Run()
|
|
#define K_INIT(Name) K_INIT_(K_CONCAT(K_UNIQUE_NAME(InitHelper), Name), K_STRINGIFY(Name))
|
|
|
|
#define K_FINALIZE_(ClassName, Name) \
|
|
class ClassName: public FinalizeHelper { \
|
|
public: \
|
|
ClassName(): FinalizeHelper(Name) {} \
|
|
void Run() override; \
|
|
}; \
|
|
static ClassName K_UNIQUE_NAME(finalize); \
|
|
void ClassName::Run()
|
|
#define K_FINALIZE(Name) K_FINALIZE_(K_CONCAT(K_UNIQUE_NAME(FinalizeHelper), Name), K_STRINGIFY(Name))
|
|
|
|
#define K_EXIT_(ClassName) \
|
|
class ClassName { \
|
|
public: \
|
|
~ClassName(); \
|
|
}; \
|
|
static ClassName K_UNIQUE_NAME(exit); \
|
|
ClassName::~ClassName()
|
|
#define K_EXIT(Name) K_EXIT_(K_CONCAT(K_UNIQUE_NAME(ExitHelper), Name))
|
|
|
|
void InitApp();
|
|
void ExitApp();
|
|
|
|
int Main(int argc, char **argv);
|
|
|
|
static inline int RunApp(int argc, char **argv)
|
|
{
|
|
K_CRITICAL(argc >= 1, "First argument is missing");
|
|
|
|
InitApp();
|
|
K_DEFER { ExitApp(); };
|
|
|
|
return Main(argc, argv);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Standard paths
|
|
// ------------------------------------------------------------------------
|
|
|
|
const char *GetUserConfigPath(const char *name, Allocator *alloc); // Can return NULL
|
|
const char *GetUserCachePath(const char *name, Allocator *alloc); // Can return NULL
|
|
const char *GetSystemConfigPath(const char *name, Allocator *alloc);
|
|
const char *GetTemporaryDirectory();
|
|
|
|
enum class FindConfigFlag {
|
|
IgnoreAppDir = 1 << 0
|
|
};
|
|
|
|
const char *FindConfigFile(const char *directory, Span<const char *const> names,
|
|
Allocator *alloc, HeapArray<const char *> *out_possibilities = nullptr);
|
|
static inline const char *FindConfigFile(Span<const char *const> names, Allocator *alloc,
|
|
HeapArray<const char *> *out_possibilities = nullptr)
|
|
{ return FindConfigFile(nullptr, names, alloc, out_possibilities); }
|
|
|
|
const char *CreateUniqueFile(Span<const char> directory, const char *prefix, const char *extension,
|
|
Allocator *alloc, int *out_fd = nullptr);
|
|
const char *CreateUniqueDirectory(Span<const char> directory, const char *prefix, Allocator *alloc);
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Parsing
|
|
// ------------------------------------------------------------------------
|
|
|
|
enum class ParseFlag {
|
|
Log = 1 << 0,
|
|
Validate = 1 << 1,
|
|
End = 1 << 2
|
|
};
|
|
#define K_DEFAULT_PARSE_FLAGS ((int)ParseFlag::Log | (int)ParseFlag::Validate | (int)ParseFlag::End)
|
|
|
|
template <typename T>
|
|
bool ParseInt(Span<const char> str, T *out_value, unsigned int flags = K_DEFAULT_PARSE_FLAGS,
|
|
Span<const char> *out_remaining = nullptr)
|
|
{
|
|
if (!str.len) [[unlikely]] {
|
|
if (flags & (int)ParseFlag::Log) {
|
|
LogError("Cannot convert empty string to integer");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
uint64_t value = 0;
|
|
|
|
Size pos = 0;
|
|
uint64_t neg = 0;
|
|
if (str.len >= 2) {
|
|
if (std::numeric_limits<T>::min() < 0 && str[0] == '-') {
|
|
pos = 1;
|
|
neg = UINT64_MAX;
|
|
} else if (str[0] == '+') {
|
|
pos = 1;
|
|
}
|
|
}
|
|
|
|
for (; pos < str.len; pos++) {
|
|
unsigned int digit = (unsigned int)(str[pos] - '0');
|
|
if (digit > 9) [[unlikely]] {
|
|
if (!pos || flags & (int)ParseFlag::End) {
|
|
if (flags & (int)ParseFlag::Log) {
|
|
LogError("Malformed integer number '%1'", str);
|
|
}
|
|
return false;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint64_t new_value = (value * 10) + digit;
|
|
if (new_value < value) [[unlikely]]
|
|
goto overflow;
|
|
value = new_value;
|
|
}
|
|
if (value > (uint64_t)std::numeric_limits<T>::max()) [[unlikely]]
|
|
goto overflow;
|
|
value = ((value ^ neg) - neg);
|
|
|
|
if (out_remaining) {
|
|
*out_remaining = str.Take(pos, str.len - pos);
|
|
}
|
|
*out_value = (T)value;
|
|
return true;
|
|
|
|
overflow:
|
|
if (flags & (int)ParseFlag::Log) {
|
|
LogError("Integer overflow for number '%1' (max = %2)", str,
|
|
std::numeric_limits<T>::max());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ParseBool(Span<const char> str, bool *out_value, unsigned int flags = K_DEFAULT_PARSE_FLAGS,
|
|
Span<const char> *out_remaining = nullptr);
|
|
|
|
bool ParseSize(Span<const char> str, int64_t *out_size, unsigned int flags = K_DEFAULT_PARSE_FLAGS,
|
|
Span<const char> *out_remaining = nullptr);
|
|
#if K_SIZE_MAX < INT64_MAX
|
|
static inline bool ParseSize(Span<const char> str, Size *out_size,
|
|
unsigned int flags = K_DEFAULT_PARSE_FLAGS, Span<const char> *out_remaining = nullptr)
|
|
{
|
|
int64_t size = 0;
|
|
if (!ParseSize(str, &size, flags, out_remaining))
|
|
return false;
|
|
|
|
if (size > K_SIZE_MAX) [[unlikely]] {
|
|
if (flags & (int)ParseFlag::Log) {
|
|
LogError("Size value is too high");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
*out_size = (Size)size;
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
bool ParseDate(Span<const char> date_str, LocalDate *out_date, unsigned int flags = K_DEFAULT_PARSE_FLAGS,
|
|
Span<const char> *out_remaining = nullptr);
|
|
|
|
bool ParseDuration(Span<const char> str, int64_t *out_duration, unsigned int flags = K_DEFAULT_PARSE_FLAGS,
|
|
Span<const char> *out_remaining = nullptr);
|
|
static inline bool ParseDuration(Span<const char> str, int *out_duration,
|
|
unsigned int flags = K_DEFAULT_PARSE_FLAGS, Span<const char> *out_remaining = nullptr)
|
|
{
|
|
int64_t duration = 0;
|
|
if (!ParseDuration(str, &duration, flags, out_remaining))
|
|
return false;
|
|
|
|
if (duration > INT_MAX) [[unlikely]] {
|
|
if (flags & (int)ParseFlag::Log) {
|
|
LogError("Duration value is too high");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
*out_duration = (int)duration;
|
|
return true;
|
|
}
|
|
|
|
bool ParseVersion(Span<const char> str, int parts, int multiplier, int64_t *out_duration,
|
|
unsigned int flags = K_DEFAULT_PARSE_FLAGS, Span<const char> *out_remaining = nullptr);
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Random
|
|
// ------------------------------------------------------------------------
|
|
|
|
void InitChaCha20(uint32_t state[16], const uint8_t key[32], const uint8_t iv[8], const uint8_t counter[8] = nullptr);
|
|
void RunChaCha20(uint32_t state[16], uint8_t out_buf[64]);
|
|
|
|
void FillRandomSafe(void *buf, Size len);
|
|
static inline void FillRandomSafe(Span<uint8_t> buf) { FillRandomSafe(buf.ptr, buf.len); }
|
|
|
|
class FastRandom {
|
|
uint64_t state[4];
|
|
|
|
public:
|
|
FastRandom();
|
|
FastRandom(uint64_t seed);
|
|
|
|
uint64_t Next();
|
|
|
|
void Fill(void *buf, Size len);
|
|
void Fill(Span<uint8_t> buf) { Fill(buf.ptr, buf.len); }
|
|
|
|
int GetInt(int min, int max);
|
|
int64_t GetInt64(int64_t min, int64_t max);
|
|
};
|
|
|
|
template <typename T>
|
|
class FastRandomRNG {
|
|
FastRandom rng;
|
|
|
|
public:
|
|
typedef T result_type;
|
|
|
|
static constexpr T min() { return std::numeric_limits<T>::min(); }
|
|
static constexpr T max() { return std::numeric_limits<T>::max(); }
|
|
|
|
T operator()()
|
|
{
|
|
T value;
|
|
rng.Fill(&value, K_SIZE(value));
|
|
return value;
|
|
}
|
|
};
|
|
|
|
uint64_t GetRandom();
|
|
int GetRandomInt(int min, int max);
|
|
int64_t GetRandomInt64(int64_t min, int64_t max);
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Sockets
|
|
// ------------------------------------------------------------------------
|
|
|
|
#if !defined(__wasi__)
|
|
|
|
enum class SocketType {
|
|
Dual,
|
|
IPv4,
|
|
IPv6,
|
|
Unix
|
|
};
|
|
static const char *const SocketTypeNames[] = {
|
|
"Dual",
|
|
"IPv4",
|
|
"IPv6",
|
|
"Unix"
|
|
};
|
|
|
|
#if defined(_WIN32)
|
|
#define SOCK_OVERLAPPED 256
|
|
#endif
|
|
|
|
#if defined(_WIN32)
|
|
bool InitWinsock();
|
|
#endif
|
|
|
|
int CreateSocket(SocketType type, int flags);
|
|
|
|
bool BindIPSocket(int sock, SocketType type, const char *addr, int port);
|
|
bool BindUnixSocket(int sock, const char *path);
|
|
bool ConnectIPSocket(int sock, const char *addr, int port);
|
|
bool ConnectUnixSocket(int sock, const char *path);
|
|
|
|
// Only for sockets on Windows
|
|
void SetDescriptorNonBlock(int fd, bool enable);
|
|
void SetDescriptorRetain(int fd, bool retain);
|
|
|
|
void CloseSocket(int fd);
|
|
|
|
#endif
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Tasks
|
|
// ------------------------------------------------------------------------
|
|
|
|
class Async {
|
|
K_DELETE_COPY(Async)
|
|
|
|
#if !defined(__wasi__)
|
|
std::atomic_bool success { true };
|
|
std::atomic_int remaining_tasks { 0 };
|
|
|
|
class AsyncPool *pool;
|
|
#else
|
|
bool success = true;
|
|
#endif
|
|
|
|
public:
|
|
Async(int threads = -1);
|
|
Async(Async *parent);
|
|
~Async();
|
|
|
|
void Run(const std::function<bool()> &f);
|
|
void Run(int worker, const std::function<bool()> &f);
|
|
|
|
bool Sync();
|
|
bool SyncSoon();
|
|
bool Wait(int timeout);
|
|
bool IsSuccess() const { return success; }
|
|
|
|
int GetWorkerCount();
|
|
|
|
static bool IsTaskRunning();
|
|
static int GetWorkerIdx();
|
|
|
|
friend class AsyncPool;
|
|
};
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Streams
|
|
// ------------------------------------------------------------------------
|
|
|
|
enum class CompressionSpeed {
|
|
Default,
|
|
Slow,
|
|
Fast
|
|
};
|
|
|
|
class StreamDecoder;
|
|
class StreamEncoder;
|
|
|
|
class StreamReader {
|
|
K_DELETE_COPY(StreamReader)
|
|
|
|
enum class SourceType {
|
|
Memory,
|
|
File,
|
|
Function
|
|
};
|
|
|
|
const char *filename = nullptr;
|
|
bool error = true;
|
|
|
|
int64_t read_total = 0;
|
|
int64_t read_max = -1;
|
|
|
|
struct {
|
|
SourceType type = SourceType::Memory;
|
|
union U {
|
|
struct {
|
|
Span<const uint8_t> buf;
|
|
Size pos;
|
|
} memory;
|
|
struct {
|
|
int fd;
|
|
bool owned;
|
|
} file;
|
|
std::function<Size(Span<uint8_t> buf)> func;
|
|
|
|
// StreamReader deals with func destructor
|
|
U() {}
|
|
~U() {}
|
|
} u;
|
|
|
|
bool eof = false;
|
|
} source;
|
|
|
|
#if !defined(__wasm__)
|
|
std::mutex mutex;
|
|
#endif
|
|
StreamDecoder *decoder = nullptr;
|
|
|
|
int64_t raw_len = -1;
|
|
Size raw_read = 0;
|
|
bool eof = false;
|
|
|
|
BlockAllocator str_alloc;
|
|
|
|
public:
|
|
StreamReader() { Close(true); }
|
|
StreamReader(Span<const uint8_t> buf, const char *filename, CompressionType compression_type = CompressionType::None)
|
|
: StreamReader() { Open(buf, filename, compression_type); }
|
|
StreamReader(int fd, const char *filename, CompressionType compression_type = CompressionType::None)
|
|
: StreamReader() { Open(fd, filename, compression_type); }
|
|
StreamReader(const char *filename, CompressionType compression_type = CompressionType::None)
|
|
: StreamReader() { Open(filename, compression_type); }
|
|
StreamReader(const std::function<Size(Span<uint8_t>)> &func, const char *filename,
|
|
CompressionType compression_type = CompressionType::None)
|
|
: StreamReader() { Open(func, filename, compression_type); }
|
|
~StreamReader() { Close(true); }
|
|
|
|
// Call before Open. Takes ownership and deletes the decoder at the end.
|
|
void SetDecoder(StreamDecoder *decoder);
|
|
|
|
bool Open(Span<const uint8_t> buf, const char *filename, CompressionType compression_type = CompressionType::None);
|
|
bool Open(int fd, const char *filename, CompressionType compression_type = CompressionType::None);
|
|
OpenResult Open(const char *filename, CompressionType compression_type = CompressionType::None);
|
|
bool Open(const std::function<Size(Span<uint8_t>)> &func, const char *filename,
|
|
CompressionType compression_type = CompressionType::None);
|
|
bool Close() { return Close(false); }
|
|
|
|
// File-specific
|
|
bool Rewind();
|
|
|
|
const char *GetFileName() const { return filename; }
|
|
int64_t GetReadLimit() { return read_max; }
|
|
bool IsValid() const { return filename && !error; }
|
|
bool IsEOF() const { return eof; }
|
|
|
|
int GetDescriptor() const;
|
|
void SetDescriptorOwned(bool owned);
|
|
|
|
void SetReadLimit(int64_t limit) { read_max = limit; }
|
|
|
|
// Thread safe methods
|
|
Size Read(Span<uint8_t> out_buf);
|
|
Size Read(Span<char> out_buf) { return Read(out_buf.As<uint8_t>()); }
|
|
|
|
// Thread safe methods
|
|
Size ReadFill(Span<uint8_t> out_buf);
|
|
Size ReadFill(Span<char> out_buf) { return ReadFill(out_buf.As<uint8_t>()); }
|
|
Size ReadFill(Size buf_len, void *out_buf) { return ReadFill(MakeSpan((uint8_t *)out_buf, buf_len)); }
|
|
|
|
Size ReadAll(Size max_len, HeapArray<uint8_t> *out_buf);
|
|
Size ReadAll(Size max_len, HeapArray<char> *out_buf)
|
|
{ return ReadAll(max_len, (HeapArray<uint8_t> *)out_buf); }
|
|
|
|
int64_t ComputeRawLen();
|
|
int64_t GetRawRead() const { return raw_read; }
|
|
|
|
private:
|
|
bool Close(bool implicit);
|
|
|
|
bool InitDecompressor(CompressionType type);
|
|
|
|
Size ReadRaw(Size max_len, void *out_buf);
|
|
|
|
friend class StreamDecoder;
|
|
};
|
|
|
|
static inline Size ReadFile(const char *filename, Span<uint8_t> out_buf)
|
|
{
|
|
StreamReader st(filename);
|
|
return st.ReadFill(out_buf);
|
|
}
|
|
static inline Size ReadFile(const char *filename, Span<char> out_buf)
|
|
{
|
|
StreamReader st(filename);
|
|
return st.ReadFill(out_buf);
|
|
}
|
|
static inline Size ReadFile(const char *filename, Size max_len, HeapArray<uint8_t> *out_buf)
|
|
{
|
|
StreamReader st(filename);
|
|
return st.ReadAll(max_len, out_buf);
|
|
}
|
|
static inline Size ReadFile(const char *filename, Size max_len, HeapArray<char> *out_buf)
|
|
{
|
|
StreamReader st(filename);
|
|
return st.ReadAll(max_len, out_buf);
|
|
}
|
|
|
|
class StreamDecoder {
|
|
protected:
|
|
StreamReader *reader;
|
|
|
|
public:
|
|
StreamDecoder(StreamReader *reader) : reader(reader) {}
|
|
virtual ~StreamDecoder() {}
|
|
|
|
virtual Size Read(Size max_len, void *out_buf) = 0;
|
|
|
|
protected:
|
|
const char *GetFileName() const { return reader->filename; }
|
|
bool IsValid() const { return reader->IsValid(); }
|
|
bool IsSourceEOF() const { return reader->source.eof; }
|
|
|
|
Size ReadRaw(Size max_len, void *out_buf) { return reader->ReadRaw(max_len, out_buf); }
|
|
|
|
void SetEOF(bool eof) { reader->eof = eof; }
|
|
};
|
|
|
|
typedef StreamDecoder *CreateDecompressorFunc(StreamReader *reader, CompressionType type);
|
|
|
|
class StreamDecompressorHelper {
|
|
public:
|
|
StreamDecompressorHelper(CompressionType type, CreateDecompressorFunc *func);
|
|
};
|
|
|
|
#define K_REGISTER_DECOMPRESSOR(Type, Cls) \
|
|
static StreamDecoder *K_UNIQUE_NAME(CreateDecompressor)(StreamReader *reader, CompressionType type) \
|
|
{ \
|
|
StreamDecoder *decompressor = new Cls(reader, type); \
|
|
return decompressor; \
|
|
} \
|
|
static StreamDecompressorHelper K_UNIQUE_NAME(CreateDecompressorHelper)((Type), K_UNIQUE_NAME(CreateDecompressor))
|
|
|
|
class LineReader {
|
|
K_DELETE_COPY(LineReader)
|
|
|
|
HeapArray<char> buf;
|
|
Span<char> view = {};
|
|
|
|
StreamReader *st;
|
|
bool error;
|
|
bool eof = false;
|
|
|
|
Span<char> line = {};
|
|
int line_number = 0;
|
|
|
|
public:
|
|
LineReader(StreamReader *st) : st(st), error(!st->IsValid()) {}
|
|
|
|
const char *GetFileName() const { return st->GetFileName(); }
|
|
int GetLineNumber() const { return line_number; }
|
|
bool IsValid() const { return !error; }
|
|
bool IsEOF() const { return eof; }
|
|
|
|
bool Next(Span<char> *out_line);
|
|
bool Next(Span<const char> *out_line) { return Next((Span<char> *)out_line); }
|
|
|
|
void PushLogFilter();
|
|
};
|
|
|
|
enum class StreamWriterFlag {
|
|
Exclusive = 1 << 0, // Only for files
|
|
Atomic = 1 << 1, // Only for files
|
|
NoBuffer = 1 << 2, // Only for files and descriptors
|
|
LineBuffer = 1 << 3 // Only for files and descriptors
|
|
};
|
|
|
|
class StreamWriter {
|
|
K_DELETE_COPY(StreamWriter)
|
|
|
|
enum class DestinationType {
|
|
Memory,
|
|
DirectFile,
|
|
LineFile,
|
|
BufferedFile,
|
|
Function
|
|
};
|
|
|
|
const char *filename = nullptr;
|
|
bool error = true;
|
|
|
|
struct {
|
|
DestinationType type = DestinationType::Memory;
|
|
union U {
|
|
struct {
|
|
HeapArray<uint8_t> *memory;
|
|
Size start;
|
|
} mem;
|
|
struct {
|
|
int fd;
|
|
bool owned;
|
|
|
|
Span<uint8_t> buf;
|
|
Size buf_used;
|
|
|
|
bool exclusive;
|
|
bool atomic;
|
|
bool unlink_on_error;
|
|
const char *tmp_filename;
|
|
} file;
|
|
std::function<bool(Span<const uint8_t>)> func;
|
|
|
|
// StreamWriter deals with func destructor
|
|
U() {}
|
|
~U() {}
|
|
} u;
|
|
|
|
bool vt100;
|
|
} dest;
|
|
|
|
#if !defined(__wasm__)
|
|
std::mutex mutex;
|
|
#endif
|
|
StreamEncoder *encoder = nullptr;
|
|
|
|
int64_t raw_written = 0;
|
|
|
|
BlockAllocator str_alloc { Kibibytes(8) };
|
|
|
|
public:
|
|
StreamWriter() { Close(true); }
|
|
StreamWriter(HeapArray<uint8_t> *mem, const char *filename, unsigned int flags = 0,
|
|
CompressionType compression_type = CompressionType::None,
|
|
CompressionSpeed compression_speed = CompressionSpeed::Default)
|
|
: StreamWriter() { Open(mem, filename, flags, compression_type, compression_speed); }
|
|
StreamWriter(HeapArray<char> *mem, const char *filename, unsigned int flags = 0,
|
|
CompressionType compression_type = CompressionType::None,
|
|
CompressionSpeed compression_speed = CompressionSpeed::Default)
|
|
: StreamWriter() { Open(mem, filename, flags, compression_type, compression_speed); }
|
|
StreamWriter(int fd, const char *filename, unsigned int flags = 0,
|
|
CompressionType compression_type = CompressionType::None,
|
|
CompressionSpeed compression_speed = CompressionSpeed::Default)
|
|
: StreamWriter() { Open(fd, filename, flags, compression_type, compression_speed); }
|
|
StreamWriter(const char *filename, unsigned int flags = 0,
|
|
CompressionType compression_type = CompressionType::None,
|
|
CompressionSpeed compression_speed = CompressionSpeed::Default)
|
|
: StreamWriter() { Open(filename, flags, compression_type, compression_speed); }
|
|
StreamWriter(const std::function<bool(Span<const uint8_t>)> &func, const char *filename, unsigned int flags = 0,
|
|
CompressionType compression_type = CompressionType::None,
|
|
CompressionSpeed compression_speed = CompressionSpeed::Default)
|
|
: StreamWriter() { Open(func, filename, flags, compression_type, compression_speed); }
|
|
~StreamWriter() { Close(true); }
|
|
|
|
// Call before Open. Takes ownership and deletes the encoder at the end.
|
|
void SetEncoder(StreamEncoder *encoder);
|
|
|
|
bool Open(HeapArray<uint8_t> *mem, const char *filename, unsigned int flags = 0,
|
|
CompressionType compression_type = CompressionType::None,
|
|
CompressionSpeed compression_speed = CompressionSpeed::Default);
|
|
bool Open(HeapArray<char> *mem, const char *filename, unsigned int flags = 0,
|
|
CompressionType compression_type = CompressionType::None,
|
|
CompressionSpeed compression_speed = CompressionSpeed::Default)
|
|
{ return Open((HeapArray<uint8_t> *)mem, filename, flags, compression_type, compression_speed); }
|
|
bool Open(int fd, const char *filename, unsigned int flags = 0,
|
|
CompressionType compression_type = CompressionType::None,
|
|
CompressionSpeed compression_speed = CompressionSpeed::Default);
|
|
bool Open(const char *filename, unsigned int flags = 0,
|
|
CompressionType compression_type = CompressionType::None,
|
|
CompressionSpeed compression_speed = CompressionSpeed::Default);
|
|
bool Open(const std::function<bool(Span<const uint8_t>)> &func, const char *filename, unsigned int flags = 0,
|
|
CompressionType compression_type = CompressionType::None,
|
|
CompressionSpeed compression_speed = CompressionSpeed::Default);
|
|
bool Close() { return Close(false); }
|
|
|
|
// File-specific
|
|
bool Rewind();
|
|
|
|
// For compressed streams, Flush may not be complete and only Close() can finalize the file.
|
|
// Thread safe method
|
|
bool Flush();
|
|
|
|
const char *GetFileName() const { return filename; }
|
|
bool IsVt100() const { return dest.vt100; }
|
|
bool IsValid() const { return filename && !error; }
|
|
|
|
int GetDescriptor() const;
|
|
void SetDescriptorOwned(bool owned);
|
|
|
|
// Thread safe methods
|
|
bool Write(Span<const uint8_t> buf);
|
|
bool Write(Span<const char> buf) { return Write(buf.As<const uint8_t>()); }
|
|
bool Write(char buf) { return Write(MakeSpan(&buf, 1)); }
|
|
bool Write(const void *buf, Size len) { return Write(MakeSpan((const uint8_t *)buf, len)); }
|
|
|
|
int64_t GetRawWritten() const { return raw_written; }
|
|
|
|
private:
|
|
bool Close(bool implicit);
|
|
|
|
void InitFile(unsigned int flags);
|
|
bool FlushBuffer();
|
|
|
|
bool InitCompressor(CompressionType type, CompressionSpeed speed);
|
|
|
|
bool WriteRaw(Span<const uint8_t> buf);
|
|
|
|
friend class StreamEncoder;
|
|
};
|
|
|
|
static inline bool WriteFile(Span<const uint8_t> buf, const char *filename, unsigned int flags = 0)
|
|
{
|
|
StreamWriter st(filename, flags);
|
|
st.Write(buf);
|
|
return st.Close();
|
|
}
|
|
static inline bool WriteFile(Span<const char> buf, const char *filename, unsigned int flags = 0)
|
|
{
|
|
StreamWriter st(filename, flags);
|
|
st.Write(buf);
|
|
return st.Close();
|
|
}
|
|
|
|
class StreamEncoder {
|
|
protected:
|
|
StreamWriter *writer;
|
|
|
|
public:
|
|
StreamEncoder(StreamWriter *writer) : writer(writer) {}
|
|
virtual ~StreamEncoder() {}
|
|
|
|
virtual bool Write(Span<const uint8_t> buf) = 0;
|
|
virtual bool Finalize() = 0;
|
|
|
|
protected:
|
|
const char *GetFileName() const { return writer->filename; }
|
|
bool IsValid() const { return writer->IsValid(); }
|
|
|
|
bool WriteRaw(Span<const uint8_t> buf) { return writer->WriteRaw(buf); }
|
|
};
|
|
|
|
typedef StreamEncoder *CreateCompressorFunc(StreamWriter *writer, CompressionType type, CompressionSpeed speed);
|
|
|
|
class StreamCompressorHelper {
|
|
public:
|
|
StreamCompressorHelper(CompressionType type, CreateCompressorFunc *func);
|
|
};
|
|
|
|
#define K_REGISTER_COMPRESSOR(Type, Cls) \
|
|
static StreamEncoder *K_UNIQUE_NAME(CreateCompressor)(StreamWriter *writer, CompressionType type, CompressionSpeed speed) \
|
|
{ \
|
|
StreamEncoder *compressor = new Cls(writer, type, speed); \
|
|
return compressor; \
|
|
} \
|
|
static StreamCompressorHelper K_UNIQUE_NAME(CreateCompressorHelper)((Type), K_UNIQUE_NAME(CreateCompressor))
|
|
|
|
bool SpliceStream(StreamReader *reader, int64_t max_len, StreamWriter *writer, Span<uint8_t> buf,
|
|
FunctionRef<void(int64_t, int64_t)> progress = [](int64_t, int64_t) {});
|
|
|
|
template<Size S = 65535>
|
|
bool SpliceStream(StreamReader *reader, int64_t max_len, StreamWriter *writer,
|
|
FunctionRef<void(int64_t, int64_t)> progress = [](int64_t, int64_t) {})
|
|
{
|
|
static_assert(S >= Kibibytes(2) && S <= Kibibytes(96));
|
|
|
|
// The default size happens to be the maximum chunk size (0xFFFF) in our HTTP chunk
|
|
// transfer implementation. musl now defaults to 128k stack size, and we ask
|
|
// for 1 MiB with PT_GNU_STACK (using linker flag -z stack-size) anyway.
|
|
uint8_t buf[S];
|
|
|
|
return SpliceStream(reader, max_len, writer, buf, progress);
|
|
}
|
|
|
|
bool IsCompressorAvailable(CompressionType compression_type);
|
|
bool IsDecompressorAvailable(CompressionType compression_type);
|
|
|
|
// ------------------------------------------------------------------------
|
|
// INI
|
|
// ------------------------------------------------------------------------
|
|
|
|
struct IniProperty {
|
|
Span<const char> section;
|
|
Span<const char> key;
|
|
Span<const char> value;
|
|
};
|
|
|
|
class IniParser {
|
|
K_DELETE_COPY(IniParser)
|
|
|
|
HeapArray<char> current_section;
|
|
|
|
enum class LineType {
|
|
Section,
|
|
KeyValue,
|
|
Exit
|
|
};
|
|
|
|
LineReader reader;
|
|
bool eof = false;
|
|
bool error = false;
|
|
|
|
public:
|
|
IniParser(StreamReader *st) : reader(st) {}
|
|
|
|
const char *GetFileName() const { return reader.GetFileName(); }
|
|
bool IsValid() const { return !error; }
|
|
bool IsEOF() const { return eof; }
|
|
|
|
bool Next(IniProperty *out_prop);
|
|
bool NextInSection(IniProperty *out_prop);
|
|
|
|
void PushLogFilter() { reader.PushLogFilter(); }
|
|
|
|
private:
|
|
LineType FindNextLine(IniProperty *out_prop);
|
|
};
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Assets
|
|
// ------------------------------------------------------------------------
|
|
|
|
// Keep in sync with version in packer.cc
|
|
struct AssetInfo {
|
|
const char *name;
|
|
CompressionType compression_type;
|
|
Span<const uint8_t> data;
|
|
|
|
K_HASHTABLE_HANDLER(AssetInfo, name);
|
|
};
|
|
|
|
#if defined(FELIX_HOT_ASSETS)
|
|
|
|
bool ReloadAssets();
|
|
Span<const AssetInfo> GetEmbedAssets();
|
|
const AssetInfo *FindEmbedAsset(const char *name);
|
|
|
|
#else
|
|
|
|
// Reserved for internal use
|
|
void InitEmbedMap(Span<const AssetInfo> assets);
|
|
|
|
extern "C" const Span<const AssetInfo> EmbedAssets;
|
|
extern HashTable<const char *, const AssetInfo *> EmbedAssetsMap;
|
|
|
|
// By definining functions here (with static inline), we don't nee EmbedAssets
|
|
// to exist unless these functions are called. It also allows the compiler to remove
|
|
// calls to ReloadAssets in non-debug builds (LTO or not).
|
|
|
|
static inline bool ReloadAssets()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static inline Span<const AssetInfo> GetEmbedAssets()
|
|
{
|
|
return EmbedAssets;
|
|
}
|
|
|
|
static inline const AssetInfo *FindEmbedAsset(const char *name)
|
|
{
|
|
InitEmbedMap(EmbedAssets);
|
|
return EmbedAssetsMap.FindValue(name, nullptr);
|
|
}
|
|
|
|
#endif
|
|
|
|
// These functions won't win any beauty or speed contest but whatever
|
|
bool PatchFile(StreamReader *reader, StreamWriter *writer,
|
|
FunctionRef<void(Span<const char>, StreamWriter *)> func);
|
|
bool PatchFile(Span<const uint8_t> data, StreamWriter *writer,
|
|
FunctionRef<void(Span<const char>, StreamWriter *)> func);
|
|
bool PatchFile(const AssetInfo &asset, StreamWriter *writer,
|
|
FunctionRef<void(Span<const char>, StreamWriter *)> func);
|
|
Span<const uint8_t> PatchFile(Span<const uint8_t> data, Allocator *alloc,
|
|
FunctionRef<void(Span<const char> key, StreamWriter *)> func);
|
|
Span<const uint8_t> PatchFile(const AssetInfo &asset, Allocator *alloc,
|
|
FunctionRef<void(Span<const char> key, StreamWriter *)> func);
|
|
Span<const char> PatchFile(Span<const char> data, Allocator *alloc,
|
|
FunctionRef<void(Span<const char> key, StreamWriter *)> func);
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Translations
|
|
// ------------------------------------------------------------------------
|
|
|
|
struct TranslationTable {
|
|
struct Pair {
|
|
const char *key;
|
|
const char *value;
|
|
};
|
|
|
|
const char *language;
|
|
Span<Pair> messages;
|
|
|
|
K_HASHTABLE_HANDLER(TranslationTable, language);
|
|
};
|
|
|
|
extern "C" const Span<const TranslationTable> TranslationTables;
|
|
|
|
void InitLocales(Span<const TranslationTable> tables, const char *default_lang);
|
|
|
|
// Resets the locale to the process default if lang is NULL or is unknown
|
|
void ChangeThreadLocale(const char *name);
|
|
const char *GetThreadLocale();
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Options
|
|
// ------------------------------------------------------------------------
|
|
|
|
struct OptionDesc {
|
|
const char *name;
|
|
const char *help;
|
|
};
|
|
|
|
enum class OptionMode {
|
|
Rotate,
|
|
Skip,
|
|
Stop
|
|
};
|
|
|
|
enum class OptionType {
|
|
NoValue,
|
|
Value,
|
|
OptionalValue
|
|
};
|
|
|
|
class OptionParser {
|
|
K_DELETE_COPY(OptionParser)
|
|
|
|
Span<const char *> args;
|
|
OptionMode mode;
|
|
|
|
Size pos = 0;
|
|
Size limit;
|
|
Size smallopt_offset = 0;
|
|
char buf[80];
|
|
|
|
bool test_failed = false;
|
|
|
|
public:
|
|
|
|
const char *current_option = nullptr;
|
|
const char *current_value = nullptr;
|
|
|
|
OptionParser(Span<const char *> args, OptionMode mode = OptionMode::Rotate)
|
|
: args(args), mode(mode), limit(args.len) {}
|
|
OptionParser(int argc, char **argv, OptionMode mode = OptionMode::Rotate)
|
|
: args((const char **)argv, argc), mode(mode), pos(1), limit(args.len) {}
|
|
|
|
Size GetPosition() const { return pos; }
|
|
|
|
const char *Next();
|
|
|
|
const char *ConsumeValue();
|
|
const char *ConsumeNonOption();
|
|
void ConsumeNonOptions(HeapArray<const char *> *non_options);
|
|
|
|
Span<const char *> GetRemainingArguments() const { return args.Take(pos, args.len - pos); }
|
|
|
|
bool Test(const char *test1, const char *test2, OptionType type = OptionType::NoValue);
|
|
bool Test(const char *test1, OptionType type = OptionType::NoValue)
|
|
{ return Test(test1, nullptr, type); }
|
|
|
|
bool TestHasFailed() const { return test_failed; }
|
|
|
|
void LogUnknownError() const;
|
|
void LogUnusedArguments() const;
|
|
};
|
|
|
|
template <typename T>
|
|
bool OptionToEnum(Span<const char *const> options, Span<const char> str, T *out_value)
|
|
{
|
|
static_assert(std::is_enum<T>::value);
|
|
|
|
for (Size i = 0; i < options.len; i++) {
|
|
const char *opt = options[i];
|
|
|
|
if (TestStr(opt, str)) {
|
|
*out_value = (T)i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
bool OptionToEnum(Span<const OptionDesc> options, Span<const char> str, T *out_value)
|
|
{
|
|
static_assert(std::is_enum<T>::value);
|
|
|
|
for (Size i = 0; i < options.len; i++) {
|
|
const OptionDesc &desc = options[i];
|
|
|
|
if (TestStr(desc.name, str)) {
|
|
*out_value = (T)i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
bool OptionToEnumI(Span<const char *const> options, Span<const char> str, T *out_value)
|
|
{
|
|
static_assert(std::is_enum<T>::value);
|
|
|
|
for (Size i = 0; i < options.len; i++) {
|
|
const char *opt = options[i];
|
|
|
|
if (TestStrI(opt, str)) {
|
|
*out_value = (T)i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
bool OptionToEnumI(Span<const OptionDesc> options, Span<const char> str, T *out_value)
|
|
{
|
|
static_assert(std::is_enum<T>::value);
|
|
|
|
for (Size i = 0; i < options.len; i++) {
|
|
const OptionDesc &desc = options[i];
|
|
|
|
if (TestStrI(desc.name, str)) {
|
|
*out_value = (T)i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
bool OptionToFlag(Span<const char *const> options, Span<const char> str, T *out_flags, bool enable = true)
|
|
{
|
|
for (Size i = 0; i < options.len; i++) {
|
|
const char *opt = options[i];
|
|
|
|
if (TestStr(opt, str)) {
|
|
*out_flags = ApplyMask(*out_flags, 1u << i, enable);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
bool OptionToFlag(Span<const OptionDesc> options, Span<const char> str, T *out_flags, bool enable = true)
|
|
{
|
|
for (Size i = 0; i < options.len; i++) {
|
|
const OptionDesc &desc = options[i];
|
|
|
|
if (TestStr(desc.name, str)) {
|
|
*out_flags = ApplyMask(*out_flags, 1u << i, enable);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
bool OptionToFlagI(Span<const char *const> options, Span<const char> str, T *out_flags, bool enable = true)
|
|
{
|
|
for (Size i = 0; i < options.len; i++) {
|
|
const char *opt = options[i];
|
|
|
|
if (TestStrI(opt, str)) {
|
|
*out_flags = ApplyMask(*out_flags, 1u << i, enable);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
bool OptionToFlagI(Span<const OptionDesc> options, Span<const char> str, T *out_flags, bool enable = true)
|
|
{
|
|
for (Size i = 0; i < options.len; i++) {
|
|
const OptionDesc &desc = options[i];
|
|
|
|
if (TestStrI(desc.name, str)) {
|
|
*out_flags = ApplyMask(*out_flags, 1u << i, enable);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Console prompter (simplified readline)
|
|
// ------------------------------------------------------------------------
|
|
|
|
struct PromptChoice {
|
|
const char *str;
|
|
char c;
|
|
};
|
|
|
|
enum class CompleteResult {
|
|
Success,
|
|
TooMany,
|
|
Error
|
|
};
|
|
|
|
struct CompleteChoice {
|
|
const char *name;
|
|
const char *value;
|
|
};
|
|
|
|
typedef CompleteResult CompleteFunc(Span<const char> value, Allocator *alloc, HeapArray<CompleteChoice> *out_choices);
|
|
|
|
class ConsolePrompter {
|
|
int prompt_columns = 0;
|
|
|
|
HeapArray<HeapArray<char>> entries;
|
|
Size entry_idx = 0;
|
|
Size str_offset = 0;
|
|
|
|
int columns = 0;
|
|
int rows = 0;
|
|
int rows_with_extra = 0;
|
|
int x = 0;
|
|
int y = 0;
|
|
|
|
const char *fake_input = "";
|
|
#if defined(_WIN32)
|
|
uint32_t surrogate_buf = 0;
|
|
#endif
|
|
|
|
public:
|
|
const char *prompt = ">>>";
|
|
const char *mask = nullptr;
|
|
std::function<CompleteFunc> complete = {};
|
|
|
|
HeapArray<char> str;
|
|
|
|
ConsolePrompter();
|
|
|
|
bool Read(Span<const char> *out_str = nullptr);
|
|
Size ReadEnum(Span<const PromptChoice> choices, Size value = 0);
|
|
|
|
void Commit();
|
|
|
|
private:
|
|
bool ReadRaw(Span<const char> *out_str);
|
|
bool ReadBuffered(Span<const char> *out_str);
|
|
|
|
Size ReadRawEnum(Span<const PromptChoice> choices, Size value);
|
|
Size ReadBufferedEnum(Span<const PromptChoice> choices);
|
|
|
|
void ChangeEntry(Size new_idx);
|
|
|
|
Size SkipForward(Size offset, Size count);
|
|
Size SkipBackward(Size offset, Size count);
|
|
Size FindForward(Size offset, const char *chars);
|
|
Size FindBackward(Size offset, const char *chars);
|
|
|
|
void Delete(Size start, Size end);
|
|
|
|
void FormatChoices(Span<const PromptChoice> choices, Size value);
|
|
|
|
void RenderRaw();
|
|
void RenderBuffered();
|
|
|
|
Vec2<int> GetConsoleSize();
|
|
int32_t ReadChar();
|
|
|
|
void EnsureNulTermination();
|
|
};
|
|
|
|
const char *Prompt(const char *prompt, const char *default_value, const char *mask, Allocator *alloc);
|
|
static inline const char *Prompt(const char *prompt, Allocator *alloc)
|
|
{ return Prompt(prompt, nullptr, nullptr, alloc); }
|
|
|
|
Size PromptEnum(const char *prompt, Span<const PromptChoice> choices, Size value = 0);
|
|
Size PromptEnum(const char *prompt, Span<const char *const> strings, Size value = 0);
|
|
|
|
// Returns -1 if cancelled, otherwise it's 1 for Yes and or 0 for No
|
|
int PromptYN(const char *prompt);
|
|
|
|
const char *PromptPath(const char *prompt, const char *default_path, Span<const char> root_directory, Allocator *alloc);
|
|
static inline const char *PromptPath(const char *prompt, Allocator *alloc)
|
|
{ return PromptPath(prompt, nullptr, GetWorkingDirectory(), alloc); }
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Mime types
|
|
// ------------------------------------------------------------------------
|
|
|
|
const char *GetMimeType(Span<const char> extension, const char *default_type = "application/octet-stream");
|
|
|
|
bool CanCompressFile(const char *filename);
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Unicode
|
|
// ------------------------------------------------------------------------
|
|
|
|
static inline int CountUtf8Bytes(char c)
|
|
{
|
|
int ones = CountLeadingZeros((uint32_t)~c << 24);
|
|
return std::min(std::max(ones, 1), 4);
|
|
}
|
|
|
|
static constexpr inline Size DecodeUtf8(const char *str, int32_t *out_c)
|
|
{
|
|
K_ASSERT(str[0]);
|
|
|
|
#define BYTE(Idx) ((uint8_t)str[Idx])
|
|
|
|
if (BYTE(0) < 0x80) {
|
|
*out_c = BYTE(0);
|
|
return 1;
|
|
} else if (BYTE(0) - 0xC2 > 0xF4 - 0xC2) [[unlikely]] {
|
|
return 0;
|
|
} else if (BYTE(1)) [[likely]] {
|
|
if (BYTE(0) < 0xE0 && (BYTE(1) & 0xC0) == 0x80) {
|
|
*out_c = ((BYTE(0) & 0x1F) << 6) | (BYTE(1) & 0x3F);
|
|
return 2;
|
|
} else if (BYTE(2)) [[likely]] {
|
|
if (BYTE(0) < 0xF0 &&
|
|
(BYTE(1) & 0xC0) == 0x80 &&
|
|
(BYTE(2) & 0xC0) == 0x80) {
|
|
*out_c = ((BYTE(0) & 0xF) << 12) | ((BYTE(1) & 0x3F) << 6) | (BYTE(2) & 0x3F);
|
|
return 3;
|
|
} else if (BYTE(3)) [[likely]] {
|
|
if ((BYTE(1) & 0xC0) == 0x80 &&
|
|
(BYTE(2) & 0xC0) == 0x80 &&
|
|
(BYTE(3) & 0xC0) == 0x80) {
|
|
*out_c = ((BYTE(0) & 0x7) << 18) | ((BYTE(1) & 0x3F) << 12) | ((BYTE(2) & 0x3F) << 6) | (BYTE(3) & 0x3F);
|
|
return 4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef BYTE
|
|
|
|
return 0;
|
|
}
|
|
|
|
static constexpr inline Size DecodeUtf8(Span<const char> str, Size offset, int32_t *out_c)
|
|
{
|
|
K_ASSERT(offset < str.len);
|
|
|
|
str = str.Take(offset, str.len - offset);
|
|
|
|
#define BYTE(Idx) ((uint8_t)str[Idx])
|
|
|
|
if (BYTE(0) < 0x80) {
|
|
*out_c = BYTE(0);
|
|
return 1;
|
|
} else if (BYTE(0) - 0xC2 > 0xF4 - 0xC2) [[unlikely]] {
|
|
return 0;
|
|
} else if (BYTE(0) < 0xE0 && str.len >= 2 && (BYTE(1) & 0xC0) == 0x80) {
|
|
*out_c = ((BYTE(0) & 0x1F) << 6) | (BYTE(1) & 0x3F);
|
|
return 2;
|
|
} else if (BYTE(0) < 0xF0 && str.len >= 3 && (BYTE(1) & 0xC0) == 0x80 &&
|
|
(BYTE(2) & 0xC0) == 0x80) {
|
|
*out_c = ((BYTE(0) & 0xF) << 12) | ((BYTE(1) & 0x3F) << 6) | (BYTE(2) & 0x3F);
|
|
return 3;
|
|
} else if (str.len >= 4 && (BYTE(1) & 0xC0) == 0x80 &&
|
|
(BYTE(2) & 0xC0) == 0x80 &&
|
|
(BYTE(3) & 0xC0) == 0x80) {
|
|
*out_c = ((BYTE(0) & 0x7) << 18) | ((BYTE(1) & 0x3F) << 12) | ((BYTE(2) & 0x3F) << 6) | (BYTE(3) & 0x3F);
|
|
return 4;
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
#undef BYTE
|
|
}
|
|
|
|
static constexpr inline int32_t DecodeUtf8(const char *str)
|
|
{
|
|
int32_t uc = -1;
|
|
DecodeUtf8(str, &uc);
|
|
return uc;
|
|
}
|
|
|
|
static inline Size EncodeUtf8(int32_t c, char out_buf[4])
|
|
{
|
|
if (c < 0x80) {
|
|
out_buf[0] = (char)c;
|
|
return 1;
|
|
} else if (c < 0x800) {
|
|
out_buf[0] = (char)(0xC0 | (c >> 6));
|
|
out_buf[1] = (char)(0x80 | (c & 0x3F));
|
|
return 2;
|
|
} else if (c >= 0xD800 && c < 0xE000) {
|
|
return 0;
|
|
} else if (c < 0x10000) {
|
|
out_buf[0] = (char)(0xE0 | (c >> 12));
|
|
out_buf[1] = (char)(0x80 | ((c >> 6) & 0x3F));
|
|
out_buf[2] = (char)(0x80 | (c & 0x3F));
|
|
return 3;
|
|
} else if (c < 0x110000) {
|
|
out_buf[0] = (char)(0xF0 | (c >> 18));
|
|
out_buf[1] = (char)(0x80 | ((c >> 12) & 0x3F));
|
|
out_buf[2] = (char)(0x80 | ((c >> 6) & 0x3F));
|
|
out_buf[3] = (char)(0x80 | (c & 0x3F));
|
|
return 4;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool IsValidUtf8(Span<const char> str);
|
|
|
|
int ComputeUnicodeWidth(Span<const char> str);
|
|
|
|
bool IsXidStart(int32_t uc);
|
|
bool IsXidContinue(int32_t uc);
|
|
|
|
// ------------------------------------------------------------------------
|
|
// CRC
|
|
// ------------------------------------------------------------------------
|
|
|
|
uint32_t CRC32(uint32_t state, Span<const uint8_t> buf);
|
|
uint32_t CRC32C(uint32_t state, Span<const uint8_t> buf);
|
|
|
|
uint64_t CRC64xz(uint64_t state, Span<const uint8_t> buf);
|
|
uint64_t CRC64nvme(uint64_t state, Span<const uint8_t> buf);
|
|
|
|
}
|