#ifndef PK_SINGLE_HEADER_FILE_H #define PK_SINGLE_HEADER_FILE_H /******************************************************************************* * PK Single-Header-Library V0.1.1 * * Author: Jonathan Bradley * Copyright: © 2024-2025 Jonathan Bradley * Description: * * A collection of useful programming tools, available for C and C++ as a * single-header file. To enable, in ONE single C or C++ file, declare * PK_IMPL_ALL before including pk.h. * * Example: * * pk.h.include.c * ``` c * #define PK_IMPL_ALL * #include "pk.h" * ``` * * It is also possible to enable modules ad-hoc by defining each IMPL * individually: * * pk.h.include.c * ``` c * # define PK_IMPL_MEM_TYPES * # define PK_IMPL_MEM * # define PK_IMPL_STR * # define PK_IMPL_EV * # define PK_IMPL_ARR * # define PK_IMPL_STN * #include "pk.h" * ``` * ******************************************************************************** * pkmacros.h: * * Provides a set of useful macros for a variety of uses. * * The macros PK_LOG* provide simple logging utilities. These can be overridden * by providing your own implementations of each and defining PK_LOG_OVERRIDE * before including pk.h Note that each of these are no-op'd if NDEBUG is * defined. * * The TypeSafeInt_H and TypeSafeInt_B macros provide a way to define * type-specific integers, implemented via enums. * ******************************************************************************** * pkmem-types.h: def PK_IMPL_MEM_TYPES before including pk.h to enable ad-hoc. * * Provides the types needed by pkmem, as well as a generic pk_handle featuring a * bucket+item indexing system. * ******************************************************************************** * pkmem.h: def PK_IMPL_MEM before including pk.h to enable ad-hoc. * * A bucketed memory manager. Allows for the creation and management of up to a * well-defined number of buckets. * * Thread safety: Bucket creation and destruction is *not* thread-safe. On the * other hand, the "pk_new" and "pk_delete" methods *are* thread-safe, but * thread-safety is implemented per-bucket via a single mutex with long-running * lock times. PRs for a more performant thread-safe strategy are welcome, * complexity and benchmark depending. * * The following definitions (shown with defaults) can be overridden: * PK_DEFAULT_BUCKET_SIZE 256MB (used when bkt is NULL on first call) * PK_MINIMUM_ALIGNMENT 1 * PK_MAXIMUM_ALIGNMENT 64 * PK_MAX_BUCKET_COUNT 8 * * For debugging purposes, define the following: * PK_MEMORY_DEBUGGER : enables a tracking system for all allocs and frees to * ensure bucket validity and consistency. * PK_MEMORY_FORCE_MALLOC : completely disables pkmem and its debugging features * in favor of directly using malloc and free. Useful for out-of-bounds * checking. * ******************************************************************************** * pkstr.h: def PK_IMPL_STR before including pk.h to enable ad-hoc. * * Provides a simple string structure, allowing the user to track the string * length and reserved buffer length. Limits max string length to uint32_t max * size, which is roughly 4GB. * * Tip: set reserved to 0 for compile-time strings as well as for strings alloc'd * in a larger buffer (such as bulk-loaded data). * ******************************************************************************** * pkev.h: def PK_IMPL_EV before including pk.h to enable ad-hoc. * * Provides a simple event callback system. While the _init and _teardown * functions are NOT thread-safe, the _register and _emit functions are. * Note: uses malloc. * * Each mgr is stored contiguously with its data. Consider the following layout: * [[mgr][ev 0][ev 1][..][ev N][ev 1 cb array][ev 2 cb array][..][ev N cb array]] * * The following definitions (shown with defaults) can be overridden: * PK_EV_INIT_MGR_COUNT 1 * PK_EV_INIT_EV_COUNT 16 * PK_EV_INIT_CB_COUNT 8 * PK_EV_GROW_RATIO 1.5 * * The number of evs and cbs (per ev) is stored as a uint8_t, so a hard-limit of * 255 is to be observed for each. The number of mgrs is stored as a uint64_t. * * Note that PK_EV_GROW_RATIO is used in two scenarios: * 1. When registering an ev on a full mgr. * 2. When registering a cb on a full ev. * The grow ratio is applied to the ev count and cb count in their respective * scenarios. This causes a new allocation for the entire mgr. The existing * mgr and its evs and cbs are copied to the new larger buffer space. * Explicitly, the number of mgrs does not grow dynamically. Use * PK_EV_INIT_MGR_COUNT to control the number of mgrs. * * Note that increasing PK_EV_INIT_MGR_COUNT isn't recommended, but you may * consider doing so if you have specific size or contiguity requirements. For * example, you could -DPK_EV_INIT_EV_COUNT=1 to reduce the memory footprint of * each event/mgr, and simply create a new mgr for each needed event. Be aware * that in this provided scenario a given mgr will still grow if a second EV is * registered. * ******************************************************************************** * pkarr.h: def PK_IMPL_ARR before including pk.h to enable ad-hoc * * Provides a structure for managing contiguous lists * * The following definitions (shown with defaults) can be overridden: * PK_ARR_INITIAL_COUNT 16 * PK_ARR_GROW_RATIO 1.5 * PK_ARR_MOVE_IN_PLACE (not defined) * * The macro `PK_ARR_MOVE_IN_PLACE` ensures that when possible, the pointer value * of `arr->data` is preserved. * It is used in the following methods: * `pk_arr_move_to_back` * `pk_arr_remove_at` * This has two additinal benefits: * 1. Minimizing the number and `sz` of calls to `pk_new` * 2. Ensuring `data[0]` to `data[(N - 1) * stride]` is not copied extraneously * to a new buffer. * The speed of this will vary depending on usage, platform, and compiler. * * Initialize `stride`, `alignment`, and `bkt` (optional) members * *before* calling any `pk_arr_*` methods. * * Examples: * ``` c * struct pk_arr arr = {0}; * arr.stride = sizeof(obj); // required * arr.alignment = alignof(obj); // required * arr.bkt = bkt; // optional * pk_arr_reserve(&arr, 10); // optional * pk_arr_append(&arr, &obj); * ``` * ``` c * struct pk_arr arr = {0}; * arr.stride = sizeof(obj); // required * arr.alignment = alignof(obj); // required * arr.bkt = bkt; // optional * pk_arr_resize(&arr, 10); * obj* d = (obj*)arr->data; * d[0] = ...; * ``` * ******************************************************************************** * pkstn.h: def PK_IMPL_STN before including pk.h to enable ad-hoc. * * Provides a thorough interface for interacting with the `stoi` family of * procedures. * ******************************************************************************** * pktmr.h: No IMPL define, all methods are macros. * * Offers a set of `pk_tmr*` macros for elapsed time checking. * *******************************************************************************/ #define PK_VERSION "0.1.1" #ifdef PK_IMPL_ALL # ifndef PK_IMPL_MEM_TYPES # define PK_IMPL_MEM_TYPES # endif # ifndef PK_IMPL_MEM # define PK_IMPL_MEM # endif # ifndef PK_IMPL_STR # define PK_IMPL_STR # endif # ifndef PK_IMPL_EV # define PK_IMPL_EV # endif # ifndef PK_IMPL_ARR # define PK_IMPL_ARR # endif # ifndef PK_IMPL_STN # define PK_IMPL_STN # endif #endif #ifndef PK_MACROS_H #define PK_MACROS_H #ifndef PK_LOG_OVERRIDE # ifdef NDEBUG # define PK_LOG_ERR(str) (void)str # define PK_LOG_INF(str) (void)str # define PK_LOGV_ERR(str, ...) (void)str # define PK_LOGV_INF(str, ...) (void)str # else # define PK_LOG_ERR(str, ...) fprintf(stderr, str) # define PK_LOG_INF(str, ...) fprintf(stdout, str) # define PK_LOGV_ERR(str, ...) fprintf(stderr, str, __VA_ARGS__) # define PK_LOGV_INF(str, ...) fprintf(stdout, str, __VA_ARGS__) # endif #endif #define PK_Q(x) #x #define PK_QUOTE(x) PK_Q(x) #define PK_CONCAT2(x, y) x##y #define PK_CONCAT(x, y) PK_CONCAT2(x, y) #define PK_HAS_FLAG(val, flag) ((val & flag) == flag) #define PK_CLAMP(val, min, max) (val < min ? min : val > max ? max : val) #define PK_MIN(val, min) (val < min ? val : min) #define PK_MAX(val, max) (val > max ? val : max) #define PK_TO_BIN_PAT PK_Q(%c%c%c%c%c%c%c%c) #define PK_TO_BIN_PAT_8 PK_TO_BIN_PAT #define PK_TO_BIN_PAT_16 PK_TO_BIN_PAT PK_TO_BIN_PAT #define PK_TO_BIN_PAT_32 PK_TO_BIN_PAT_16 PK_TO_BIN_PAT_16 #define PK_TO_BIN_PAT_64 PK_TO_BIN_PAT_32 PK_TO_BIN_PAT_32 #define PK_TO_BIN(byte) \ ((byte) & 0x80 ? '1' : '0'), \ ((byte) & 0x40 ? '1' : '0'), \ ((byte) & 0x20 ? '1' : '0'), \ ((byte) & 0x10 ? '1' : '0'), \ ((byte) & 0x08 ? '1' : '0'), \ ((byte) & 0x04 ? '1' : '0'), \ ((byte) & 0x02 ? '1' : '0'), \ ((byte) & 0x01 ? '1' : '0') #define PK_TO_BIN_8(u8) PK_TO_BIN(u8) #define PK_TO_BIN_16(u16) PK_TO_BIN((u16 >> 8)), PK_TO_BIN(u16 & 0x00FF) #define PK_TO_BIN_32(u32) PK_TO_BIN_16((u32 >> 16)), PK_TO_BIN_16(u32 & 0x0000FFFF) #define PK_TO_BIN_64(u64) PK_TO_BIN_32((u64 >> 32)), PK_TO_BIN_32(u64 & 0x00000000FFFFFFFF) #if defined(__cplusplus) # define CAFE_BABE(T) reinterpret_cast(0xCAFEBABE) #else # define CAFE_BABE(T) (T *)(0xCAFEBABE) #endif #define NULL_CHAR_ARR(v, len) char v[len]; v[0] = '\0'; v[len-1] = '\0'; #define IS_CONSTRUCTIBLE(T) constexpr(std::is_default_constructible::value && !std::is_integral::value && !std::is_floating_point::value) #define IS_DESTRUCTIBLE(T) constexpr(std::is_destructible::value && !std::is_integral::value && !std::is_floating_point::value && !std::is_array::value) #define TypeSafeInt2_H(TypeName, Type, Max, TypeName_T, TypeName_MAX, TypeName_T_MAX) \ using TypeName_T = Type; \ enum class TypeName : TypeName_T; \ constexpr TypeName_T TypeName_T_MAX = TypeName_T{Max}; \ constexpr TypeName TypeName_MAX = TypeName{TypeName_T_MAX}; \ TypeName operator+(const TypeName& a, const TypeName& b); \ TypeName operator-(const TypeName& a, const TypeName& b); \ TypeName operator&(const TypeName& a, const TypeName& b); \ TypeName operator|(const TypeName& a, const TypeName& b); \ TypeName operator^(const TypeName& a, const TypeName& b); \ TypeName& operator++(TypeName& a); \ TypeName& operator--(TypeName& a); \ TypeName operator++(TypeName& a, int); \ TypeName operator--(TypeName& a, int); \ TypeName operator<<(const TypeName& a, const TypeName& b); \ TypeName operator>>(const TypeName& a, const TypeName& b); \ TypeName operator+=(TypeName& a, const TypeName& b); \ TypeName operator-=(TypeName& a, const TypeName& b); \ TypeName operator&=(TypeName& a, const TypeName& b); \ TypeName operator|=(TypeName& a, const TypeName& b); \ TypeName operator^=(TypeName& a, const TypeName& b); \ TypeName operator~(TypeName& a); #define TypeSafeInt2_B(TypeName, TypeName_T) \ inline TypeName operator+(const TypeName& a, const TypeName& b) { \ return TypeName(static_cast(a) + static_cast(b)); \ } \ inline TypeName operator-(const TypeName& a, const TypeName& b) { \ return TypeName(static_cast(a) - static_cast(b)); \ } \ inline TypeName operator&(const TypeName& a, const TypeName& b) { \ return TypeName(static_cast(a) & static_cast(b)); \ } \ inline TypeName operator|(const TypeName& a, const TypeName& b) { \ return TypeName(static_cast(a) | static_cast(b)); \ } \ inline TypeName operator^(const TypeName& a, const TypeName& b) { \ return TypeName(static_cast(a) ^ static_cast(b)); \ } \ inline TypeName& operator++(TypeName& a) { \ a = a + TypeName{1}; \ return a; \ } \ inline TypeName& operator--(TypeName& a) { \ a = a - TypeName{1}; \ return a; \ }; \ inline TypeName operator++(TypeName& a, int) { \ a = a + TypeName{1}; \ return a; \ } \ inline TypeName operator--(TypeName& a, int) { \ a = a - TypeName{1}; \ return a; \ }; \ inline TypeName operator<<(const TypeName& a, const TypeName& b) { \ return TypeName(static_cast(a) << static_cast(b)); \ }; \ inline TypeName operator>>(const TypeName& a, const TypeName& b) { \ return TypeName(static_cast(a) >> static_cast(b)); \ }; \ inline TypeName operator+=(TypeName& a, const TypeName& b) { \ a = TypeName{a + b}; \ return a; \ }; \ inline TypeName operator-=(TypeName& a, const TypeName& b) { \ a = TypeName{a - b}; \ return a; \ }; \ inline TypeName operator&=(TypeName& a, const TypeName& b) { \ a = TypeName{a & b}; \ return a; \ }; \ inline TypeName operator|=(TypeName& a, const TypeName& b) { \ a = TypeName{a | b}; \ return a; \ }; \ inline TypeName operator^=(TypeName& a, const TypeName& b) { \ a = TypeName{a ^ b}; \ return a; \ }; \ inline TypeName operator~(TypeName& a) { \ a = static_cast(~static_cast(a)); \ return a; \ }; #define TypeSafeInt_H(TypeName, Type, Max) \ TypeSafeInt2_H(TypeName, Type, Max, PK_CONCAT(TypeName, _T), PK_CONCAT(TypeName, _MAX), PK_CONCAT(TypeName, _T_MAX)) #define TypeSafeInt_B(TypeName) \ TypeSafeInt2_B(TypeName, PK_CONCAT(TypeName, _T)) #define TypeSafeInt2_H_constexpr(TypeName, Type, Max, TypeName_T, TypeName_MAX, TypeName_T_MAX) \ using TypeName_T = Type; \ enum class TypeName : TypeName_T; \ constexpr TypeName_T TypeName_T_MAX = TypeName_T{Max}; \ constexpr TypeName TypeName_MAX = TypeName{TypeName_T_MAX}; \ constexpr TypeName operator+(const TypeName& a, const TypeName& b) { \ return TypeName(static_cast(a) + static_cast(b)); \ } \ constexpr TypeName operator-(const TypeName& a, const TypeName& b) { \ return TypeName(static_cast(a) - static_cast(b)); \ } \ constexpr TypeName operator&(const TypeName& a, const TypeName& b) { \ return TypeName(static_cast(a) & static_cast(b)); \ } \ constexpr TypeName operator|(const TypeName& a, const TypeName& b) { \ return TypeName(static_cast(a) | static_cast(b)); \ } \ constexpr TypeName operator^(const TypeName& a, const TypeName& b) { \ return TypeName(static_cast(a) ^ static_cast(b)); \ } \ constexpr TypeName& operator++(TypeName& a) { \ a = a + TypeName{1}; \ return a; \ } \ constexpr TypeName& operator--(TypeName& a) { \ a = a - TypeName{1}; \ return a; \ }; \ constexpr TypeName operator++(TypeName& a, int) { \ a = a + TypeName{1}; \ return a; \ } \ constexpr TypeName operator--(TypeName& a, int) { \ a = a - TypeName{1}; \ return a; \ }; \ constexpr TypeName operator<<(const TypeName& a, const TypeName& b) { \ return TypeName(static_cast(a) << static_cast(b)); \ }; \ constexpr TypeName operator>>(const TypeName& a, const TypeName& b) { \ return TypeName(static_cast(a) >> static_cast(b)); \ }; \ constexpr TypeName operator+=(TypeName& a, const TypeName& b) { \ a = TypeName{a + b}; \ return a; \ }; \ constexpr TypeName operator-=(TypeName& a, const TypeName& b) { \ a = TypeName{a - b}; \ return a; \ }; \ constexpr TypeName operator&=(TypeName& a, const TypeName& b) { \ a = TypeName{a & b}; \ return a; \ }; \ constexpr TypeName operator|=(TypeName& a, const TypeName& b) { \ a = TypeName{a | b}; \ return a; \ }; \ constexpr TypeName operator^=(TypeName& a, const TypeName& b) { \ a = TypeName{a ^ b}; \ return a; \ }; \ constexpr TypeName operator~(const TypeName& a) { \ return static_cast(~static_cast(a)); \ }; #define TypeSafeInt_constexpr(TypeName, Type, Max) \ TypeSafeInt2_H_constexpr(TypeName, Type, Max, PK_CONCAT(TypeName, _T), PK_CONCAT(TypeName, _MAX), PK_CONCAT(TypeName, _T_MAX)) #endif /* PK_MACROS_H */ #ifndef PK_MEM_TYPES_H #define PK_MEM_TYPES_H #include typedef uint32_t pk_handle_bucket_index_T; typedef uint32_t pk_handle_item_index_T; enum PK_HANDLE_VALIDATION : uint8_t { PK_HANDLE_VALIDATION_VALID = 0, PK_HANDLE_VALIDATION_BUCKET_INDEX_TOO_HIGH = 1, PK_HANDLE_VALIDATION_ITEM_INDEX_TOO_HIGH = 2, PK_HANDLE_VALIDATION_VALUE_MAX = 3, }; struct pk_handle { pk_handle_bucket_index_T bucketIndex; pk_handle_item_index_T itemIndex; }; #define PK_HANDLE_MAX ((struct pk_handle){ .bucketIndex = 0xFFFFFFFF, .itemIndex = 0xFFFFFFFF }) enum PK_HANDLE_VALIDATION pk_handle_validate(const struct pk_handle handle, const struct pk_handle bucketHandle, const uint64_t maxItems); #if defined(__cplusplus) constexpr struct pk_handle pk_handle_MAX_constexpr = (struct pk_handle){ .bucketIndex = 0xFFFFFFFF, .itemIndex = 0xFFFFFFFF }; inline constexpr bool operator==(const pk_handle& lhs, const pk_handle& rhs) { return lhs.bucketIndex == rhs.bucketIndex && lhs.itemIndex == rhs.itemIndex; } template inline constexpr enum PK_HANDLE_VALIDATION pk_handle_validate_constexpr() { if constexpr (handle == pk_handle_MAX_constexpr) return PK_HANDLE_VALIDATION_VALUE_MAX; if constexpr (handle.bucketIndex > bucketHandle.bucketIndex) return PK_HANDLE_VALIDATION_BUCKET_INDEX_TOO_HIGH; if constexpr (handle.itemIndex > maxItems) return PK_HANDLE_VALIDATION_ITEM_INDEX_TOO_HIGH; if constexpr (handle.bucketIndex == bucketHandle.bucketIndex && handle.itemIndex > bucketHandle.itemIndex) return PK_HANDLE_VALIDATION_ITEM_INDEX_TOO_HIGH; return PK_HANDLE_VALIDATION_VALID; } #endif /* __cplusplus */ struct pk_membucket; #endif /* PK_MEM_TYPES_H */ #ifdef PK_IMPL_MEM_TYPES enum PK_HANDLE_VALIDATION pk_handle_validate(const struct pk_handle handle, const struct pk_handle bucketHandle, const uint64_t maxItems) { if (handle.bucketIndex == PK_HANDLE_MAX.bucketIndex && handle.itemIndex == PK_HANDLE_MAX.itemIndex) return PK_HANDLE_VALIDATION_VALUE_MAX; if (handle.bucketIndex > bucketHandle.bucketIndex) return PK_HANDLE_VALIDATION_BUCKET_INDEX_TOO_HIGH; if (handle.itemIndex > maxItems) return PK_HANDLE_VALIDATION_ITEM_INDEX_TOO_HIGH; if (handle.bucketIndex == bucketHandle.bucketIndex && handle.itemIndex > bucketHandle.itemIndex) return PK_HANDLE_VALIDATION_ITEM_INDEX_TOO_HIGH; return PK_HANDLE_VALIDATION_VALID; } #endif /* PK_IMPL_MEM_TYPES */ #ifndef PK_MEM_H #define PK_MEM_H #include #include #ifndef PK_DEFAULT_BUCKET_SIZE # define PK_DEFAULT_BUCKET_SIZE (1ULL * 1024ULL * 1024ULL * 256ULL) #endif #ifndef PK_MINIMUM_ALIGNMENT # define PK_MINIMUM_ALIGNMENT 1 #endif #ifndef PK_MAXIMUM_ALIGNMENT # define PK_MAXIMUM_ALIGNMENT 64 #endif struct pk_membucket* pk_bucket_create(const char* description, int64_t sz, bool transient); void pk_bucket_destroy(struct pk_membucket* bkt); void pk_bucket_reset(struct pk_membucket* bkt); void pk_memory_debug_print(); void pk_memory_flush(); void pk_memory_teardown_all(); bool pk_memory_is_in_bucket(const void* ptr, const struct pk_membucket* bkt); void* pk_new_base(size_t sz, size_t alignment); void* pk_new_bkt(size_t sz, size_t alignment, struct pk_membucket* bkt); void* pk_new(size_t sz, size_t alignment, struct pk_membucket* bkt); void pk_delete_base(const void* ptr, size_t sz); void pk_delete_bkt(const void* ptr, size_t sz, struct pk_membucket* bkt); void pk_delete(const void* ptr, size_t sz, struct pk_membucket* bkt); #if defined(__cplusplus) #include static inline void stupid_header_warnings_cpp() { (void)std::is_const::value; } template inline T* pk_new(pk_membucket* bucket = nullptr) { void* ptr = nullptr; if (bucket) { ptr = pk_new_bkt(sizeof(T), alignof(T), bucket); } else { ptr = pk_new_base(sizeof(T), alignof(T)); } if IS_CONSTRUCTIBLE(T) { return new (ptr) T{}; } return reinterpret_cast(ptr); } template inline T* pk_new(long count, pk_membucket* bucket = nullptr) { char* ptr = nullptr; if (bucket) { ptr = static_cast(pk_new_bkt(sizeof(T) * count, alignof(T), bucket)); } else { ptr = static_cast(pk_new_base(sizeof(T) * count, alignof(T))); } if IS_CONSTRUCTIBLE(T) { for (long i = 0; i < count; ++i) { new (ptr + (i * sizeof(T))) T{}; } } return reinterpret_cast(ptr); } template inline void pk_delete(const T* ptr, pk_membucket* bucket = nullptr) { if IS_DESTRUCTIBLE(T) { reinterpret_cast(ptr)->~T(); } if (bucket) { return pk_delete_bkt(static_cast(ptr), sizeof(T), bucket); } else { return pk_delete_base(static_cast(ptr), sizeof(T)); } } template inline void pk_delete(const T* ptr, long count, pk_membucket* bucket = nullptr) { if IS_DESTRUCTIBLE(T) { for (long i = 0; i < count; ++i) { reinterpret_cast(reinterpret_cast(ptr) + (i * sizeof(T)))->~T(); } } if (bucket) { return pk_delete_bkt(static_cast(ptr), sizeof(T) * count, bucket); } else { return pk_delete_base(static_cast(ptr), sizeof(T) * count); } } #endif /* __cplusplus */ #endif /* PK_MEM */ #ifdef PK_IMPL_MEM #include #include #include #include static inline void pkmem_stupid_header_warnings() { (void)stdout; } #if defined(PK_MEMORY_DEBUGGER) /* * Note that certain aspects of this expect that you only have one non-transient bucket. * If you need to track multiple non-transient buckets, these sections will need a refactor. */ #endif #ifndef PK_MAX_BUCKET_COUNT # define PK_MAX_BUCKET_COUNT 8 #endif struct pk_memblock { char* data; size_t size; }; struct pk_membucket { // the total size of the bucket, `blocks+ptr` int64_t size; // the current head of the bucket: byte offset from `ptr`. // All currently alloc'd data is before this offset int64_t head; // amount of lost bytes in this membucket, hopefully zero int64_t lostBytes; // the number of active allocations from this bucket int64_t allocs; // the index of the last empty block. // Should always point to `pk_memblock{ .data = ptr+head, .size=size-head }` int64_t lastEmptyBlockIndex; // number of pk_memblocks in the `*blocks` array int64_t maxBlockCount; // ptr to an array of pk_memblock to track ALL free space between ptr and ptr+sz struct pk_memblock* blocks; // starting point for alloc'd data union { char* ptr; void* raw; }; const char* description; mtx_t mtx; bool transient; }; static struct pk_membucket pk_buckets[PK_MAX_BUCKET_COUNT]; static int64_t pk_bucket_head = 0; #ifdef PK_MEMORY_DEBUGGER struct pk_dbg_memblock { struct pk_memblock blk; struct pk_membucket *bkt; }; static struct pk_dbg_memblock debug_all_allocs[1024 * 1024]; static int64_t debug_alloc_head = 0; static bool has_init_debug = false; #endif bool pk_memory_is_in_bucket(const void* ptr, const struct pk_membucket* bkt) { if (ptr >= bkt->raw && (const char*)ptr < bkt->ptr + bkt->size) return true; return false; } void pk_memory_debug_print() { PK_LOGV_INF("Memory Manager printout:\nBucket count: %li\n", pk_bucket_head); for (long i = 0; i < pk_bucket_head; ++i) { PK_LOGV_INF("- bucket #%li\n", i); PK_LOGV_INF("\tdescription: %s\n", pk_buckets[i].description); PK_LOGV_INF("\tsize: %li\n", pk_buckets[i].size); PK_LOGV_INF("\thead: %li\n", pk_buckets[i].head); PK_LOGV_INF("\tlostBytes: %li\n", pk_buckets[i].lostBytes); PK_LOGV_INF("\tallocs: %li\n", pk_buckets[i].allocs); PK_LOGV_INF("\tlastEmptyBlockIndex: %li\n", pk_buckets[i].lastEmptyBlockIndex); PK_LOGV_INF("\tmaxBlockCount: %li\n", pk_buckets[i].maxBlockCount); PK_LOGV_INF("\tblocks: %p\n", pk_buckets[i].blocks); PK_LOGV_INF("\tptr: %p\n", pk_buckets[i].ptr); PK_LOGV_INF("\ttransient: %i\n", pk_buckets[i].transient); #ifdef PK_MEMORY_DEBUGGER uint64_t count = 0; for (int64_t d = 0; d < debug_alloc_head; ++d) { if (debug_all_allocs[d].bkt == &pk_buckets[d] && debug_all_allocs[d].blk.size > 0) { count += 1; } } PK_LOGV_INF("\tdebug alloc count: %lu\n", count); PK_LOGV_INF("\tdebug alloc last: %lu\n", debug_alloc_head); #endif } } void pk_memory_flush() { for (long i = pk_bucket_head - 1; i > -1; --i) { if (pk_buckets[i].head != 0) break; if (pk_buckets[i].transient == true) break; pk_bucket_head--; if (pk_buckets[i].raw == CAFE_BABE(void)) continue; pk_bucket_destroy(&pk_buckets[i]); } } void pk_memory_teardown_all() { for (int64_t i = pk_bucket_head; i > 0; --i) { if (pk_buckets[i - 1].ptr == nullptr) continue; if (pk_buckets[i - 1].ptr == CAFE_BABE(char)) continue; pk_bucket_destroy(&pk_buckets[i - 1]); } pk_bucket_head = 0; } static int64_t pk_bucket_create_inner(int64_t sz, bool transient, const char* description) { assert(pk_bucket_head < PK_MAX_BUCKET_COUNT && "pkmem.h: reserved bucket count exceeded"); #ifdef PK_MEMORY_DEBUGGER if (has_init_debug == false) { has_init_debug = true; memset(debug_all_allocs, 0, sizeof(struct pk_dbg_memblock) * 1024 * 1024); } #endif int64_t blockCount = sz * 0.01; struct pk_membucket* bkt = &pk_buckets[pk_bucket_head]; bkt->size = sz; bkt->head = 0; bkt->lostBytes = 0; bkt->allocs = 0; bkt->lastEmptyBlockIndex = 0; bkt->maxBlockCount = blockCount < 10 ? 10 : blockCount; bkt->blocks = (struct pk_memblock*)malloc(sz); mtx_init(&bkt->mtx, mtx_plain); assert(bkt->blocks != nullptr && "failed to allocate memory"); #if 1 memset(bkt->blocks, 0, sz); #endif bkt->ptr = ((char*)(bkt->blocks)) + (sizeof(struct pk_memblock) * bkt->maxBlockCount); size_t misalignment = (uint64_t)(bkt->ptr) % PK_MAXIMUM_ALIGNMENT; if (misalignment != 0) { size_t moreBlocks = misalignment / sizeof(struct pk_memblock); bkt->maxBlockCount += moreBlocks; bkt->ptr += (PK_MAXIMUM_ALIGNMENT - misalignment); } bkt->description = description; bkt->transient = transient; struct pk_memblock* memBlock = (struct pk_memblock*)(bkt->blocks); memBlock->data = bkt->ptr; memBlock->size = sz - (sizeof(struct pk_memblock) * bkt->maxBlockCount); return pk_bucket_head++; } struct pk_membucket* pk_bucket_create(const char* description, int64_t sz, bool transient) { return &pk_buckets[pk_bucket_create_inner(sz, transient, description)]; } void pk_bucket_destroy(struct pk_membucket* bkt) { int64_t i; for (i = 0; i < pk_bucket_head; ++i) { if (&pk_buckets[i] == bkt) { if (pk_bucket_head == i + 1) pk_bucket_head--; break; } } free(bkt->blocks); bkt->size = 0; bkt->head = 0; bkt->lostBytes = 0; bkt->allocs = 0; bkt->lastEmptyBlockIndex = -1; bkt->maxBlockCount = 0; bkt->blocks = CAFE_BABE(struct pk_memblock); bkt->ptr = CAFE_BABE(char); bkt->transient = false; mtx_destroy(&bkt->mtx); #ifdef PK_MEMORY_DEBUGGER for (i = debug_alloc_head; i > -1; --i) { if (debug_all_allocs[i].bkt == bkt) { debug_all_allocs[i].blk.data = NULL; debug_all_allocs[i].blk.size = 0u; } } #endif } void pk_bucket_reset(struct pk_membucket* bkt) { #ifdef PK_MEMORY_DEBUGGER int64_t i; #endif if (bkt->transient != true) { PK_LOG_ERR("WARNING: pk_bucket_reset called on non-transient pk_membucket\n"); } bkt->head = 0; bkt->lostBytes = 0; bkt->allocs = 0; bkt->lastEmptyBlockIndex = 0; bkt->blocks->data = bkt->ptr; bkt->blocks->size = bkt->size - (sizeof(struct pk_memblock) * bkt->maxBlockCount); #ifdef PK_MEMORY_DEBUGGER for (i = debug_alloc_head; i > -1; --i) { if (debug_all_allocs[i].bkt == bkt) { debug_all_allocs[i].blk.data = NULL; debug_all_allocs[i].blk.size = 0u; } } #endif } void pk_bucket_insert_block(struct pk_membucket* bkt, const struct pk_memblock* block) { int64_t index = bkt->lastEmptyBlockIndex; while (index >= 0) { struct pk_memblock* b = &bkt->blocks[index]; struct pk_memblock* nb = &bkt->blocks[index + 1]; if (b->data < block->data) { break; } nb->data = b->data; nb->size = b->size; index -= 1; } struct pk_memblock *b = &bkt->blocks[index + 1]; b->data = block->data; b->size = block->size; bkt->lastEmptyBlockIndex += 1; } void pk_bucket_collapse_empty_blocks(struct pk_membucket* bkt) { for (int64_t i = bkt->lastEmptyBlockIndex; i > -1; --i) { struct pk_memblock* block = &bkt->blocks[i]; if (block->size == 0 && i == bkt->lastEmptyBlockIndex) { block->data = nullptr; bkt->lastEmptyBlockIndex -= 1; continue; } if (block->size > 0) { continue; } for (int64_t k = i; k < bkt->lastEmptyBlockIndex; ++k) { bkt->blocks[k].data = bkt->blocks[k + 1].data; bkt->blocks[k].size = bkt->blocks[k + 1].size; } bkt->lastEmptyBlockIndex -= 1; } } void* pk_new_bkt(size_t sz, size_t alignment, struct pk_membucket* bkt) { #ifdef PK_MEMORY_FORCE_MALLOC return malloc(sz); #endif if (sz == 0) return nullptr; size_t calculatedAlignment = alignment < PK_MINIMUM_ALIGNMENT ? PK_MINIMUM_ALIGNMENT : alignment; size_t misalignment = 0; struct pk_memblock* prevBlock = nullptr; struct pk_memblock* block = nullptr; struct pk_memblock* nextBlock = nullptr; void* data = nullptr; mtx_lock(&bkt->mtx); for (int64_t i = 0; i <= bkt->lastEmptyBlockIndex; ++i) { struct pk_memblock* blk = &bkt->blocks[i]; misalignment = (size_t)(blk->data) % calculatedAlignment; misalignment = (calculatedAlignment - misalignment) % calculatedAlignment; if (blk->size >= sz + misalignment) { block = blk; if (i < bkt->lastEmptyBlockIndex && bkt->blocks[i + 1].data == block->data + block->size) { nextBlock = &bkt->blocks[i + 1]; } if (i > 0 && i != bkt->lastEmptyBlockIndex && (bkt->blocks[i-1].data + bkt->blocks[i-1].size) == block->data) { prevBlock = &bkt->blocks[i - 1]; } break; } } if (block == nullptr) { mtx_unlock(&bkt->mtx); assert(block != nullptr && "memory corruption: not enough space in chosen bkt"); } data = block->data + misalignment; #ifdef PK_MEMORY_DEBUGGER bool handled = bkt->transient; if (handled == false) { for (int64_t i = 0; i < debug_alloc_head; ++i) { struct pk_dbg_memblock* mb = &debug_all_allocs[i]; if (mb->bkt != NULL) continue; assert((mb->blk.size == 0 || (void*)(mb->blk.data) != data) && "mem address alloc'd twice!"); if (mb->blk.size == 0) { mb->blk.data = (char*)(data); mb->blk.size = sz; mb->bkt = bkt; handled = true; break; } } } if (handled == false) { debug_all_allocs[debug_alloc_head++] = (struct pk_dbg_memblock){ .blk = (struct pk_memblock) { .data = (char*)(data), .size = sz, }, .bkt = bkt, }; } #endif int64_t afterSize = block->size - (misalignment + sz); if (block->data == bkt->ptr + bkt->head) { bkt->head += (sz + misalignment); } if (afterSize > 0 && nextBlock == nullptr) { struct pk_memblock newBlock; memset(&newBlock, 0, sizeof(struct pk_memblock)); newBlock.data = block->data + misalignment + sz; newBlock.size = afterSize; pk_bucket_insert_block(bkt, &newBlock); } if (prevBlock == nullptr && nextBlock == nullptr) { block->size = misalignment; } else if (nextBlock != nullptr) { block->size = misalignment; nextBlock->data -= afterSize; nextBlock->size += afterSize; } else if (prevBlock != nullptr) { prevBlock->size += misalignment; block->data += misalignment + sz; block->size = 0; // if you make it here, afterSize has already been handled } bkt->allocs++; assert(data >= bkt->raw && "allocated data is before bucket data"); assert((char*)data <= bkt->ptr + bkt->size && "allocated data is after bucket data"); pk_bucket_collapse_empty_blocks(bkt); #ifdef PK_MEMORY_DEBUGGER if (!bkt->transient) { int64_t debug_tracked_alloc_size = 0; int64_t debug_bucket_alloc_size = bkt->size - (sizeof(struct pk_memblock) * bkt->maxBlockCount); for (int64_t i = 0; i < debug_alloc_head; ++i) { if (debug_all_allocs[i].bkt != bkt) continue; debug_tracked_alloc_size += debug_all_allocs[i].blk.size; } for (int64_t i = 0; i <= bkt->lastEmptyBlockIndex; ++i) { debug_bucket_alloc_size -= bkt->blocks[i].size; } assert(debug_tracked_alloc_size == debug_bucket_alloc_size && "allocation size mismatch!"); } #endif mtx_unlock(&bkt->mtx); return data; } void* pk_new_base(size_t sz, size_t alignment) { struct pk_membucket* bkt = nullptr; for (long i = 0; i < pk_bucket_head; ++i) { if (pk_buckets[i].transient == false && pk_buckets[i].size - pk_buckets[i].head > sz + PK_MAXIMUM_ALIGNMENT) { bkt = &pk_buckets[i]; break; } } if (bkt == nullptr) { bkt = &pk_buckets[pk_bucket_create_inner(PK_DEFAULT_BUCKET_SIZE, false, "pk_bucket internally created")]; } return pk_new_bkt(sz, alignment, bkt); } void* pk_new(size_t sz, size_t alignment, struct pk_membucket* bkt) { if (bkt != NULL) return pk_new_bkt(sz, alignment, bkt); return pk_new_base(sz, alignment); } void pk_delete_bkt(const void* ptr, size_t sz, struct pk_membucket* bkt) { #ifdef PK_MEMORY_FORCE_MALLOC return std::free(const_cast(ptr)); #endif mtx_lock(&bkt->mtx); assert(ptr >= bkt->raw && (char*)ptr < bkt->ptr + bkt->size && "pointer not in memory bucket range"); assert(sz > 0 && "attempted to free pointer of size 0"); #ifdef PK_MEMORY_DEBUGGER bool found = bkt->transient; if (found == false) { for (int64_t i = debug_alloc_head - 1; i > -1; --i) { struct pk_dbg_memblock* mb = &debug_all_allocs[i]; if (mb->bkt != bkt) continue; if (mb->blk.size == 0) continue; if ((void*)(mb->blk.data) == ptr) { assert(mb->blk.size == sz && "[pkmem.h] incorrect free size"); mb->blk.size = 0; mb->bkt = NULL; found = true; if (i == (debug_alloc_head - 1)) { debug_alloc_head--; } break; } } } assert(found && "[pkmem.h] double free or invalid ptr"); #endif bkt->allocs--; if (bkt->allocs == 0) { bkt->head = 0; bkt->lastEmptyBlockIndex = 0; bkt->blocks[0].data = bkt->ptr; bkt->blocks[0].size = bkt->size - (sizeof(struct pk_memblock) * bkt->maxBlockCount); return; } char* afterPtr = ((char*)(ptr))+sz; struct pk_memblock* beforeBlk = nullptr; struct pk_memblock* afterBlk = nullptr; for (int64_t i = bkt->lastEmptyBlockIndex; i > 0; --i) { if (bkt->blocks[i-1].data + bkt->blocks[i-1].size == ptr) { beforeBlk = &bkt->blocks[i-1]; } if (bkt->blocks[i].data == afterPtr) { afterBlk = &bkt->blocks[i]; break; } if (bkt->blocks[i-1].data < (char*)ptr) { break; } } if (ptr == bkt->ptr && afterBlk == nullptr && bkt->blocks[0].data == afterPtr) { afterBlk = &bkt->blocks[0]; } if (afterBlk != nullptr && afterBlk->data == bkt->ptr + bkt->head) { bkt->head -= sz; if (beforeBlk != nullptr) { bkt->head -= beforeBlk->size; } } if (beforeBlk == nullptr && afterBlk == nullptr) { struct pk_memblock newBlock; memset(&newBlock, 0, sizeof(struct pk_memblock)); newBlock.data = (char*)ptr; newBlock.size = sz; pk_bucket_insert_block(bkt, &newBlock); } else if (beforeBlk != nullptr && afterBlk != nullptr) { beforeBlk->size += sz + afterBlk->size; afterBlk->size = 0; } else if (beforeBlk != nullptr) { beforeBlk->size += sz; } else if (afterBlk != nullptr) { afterBlk->data -= sz; afterBlk->size += sz; } pk_bucket_collapse_empty_blocks(bkt); #ifdef PK_MEMORY_DEBUGGER if (!bkt->transient) { int64_t debug_tracked_alloc_size = 0; int64_t debug_bucket_alloc_size = bkt->size - (sizeof(struct pk_memblock) * bkt->maxBlockCount); for (int64_t i = 0; i < debug_alloc_head; ++i) { if (debug_all_allocs[i].bkt != bkt) continue; debug_tracked_alloc_size += debug_all_allocs[i].blk.size; } for (int64_t i = 0; i <= bkt->lastEmptyBlockIndex; ++i) { debug_bucket_alloc_size -= bkt->blocks[i].size; } assert(debug_tracked_alloc_size == debug_bucket_alloc_size && "allocation size mismatch!"); } #endif mtx_unlock(&bkt->mtx); } void pk_delete_base(const void* ptr, size_t sz) { struct pk_membucket* bkt = nullptr; for (long i = 0; i < pk_bucket_head; ++i) { bkt = &pk_buckets[i]; if (ptr >= bkt->raw && (char*)ptr < bkt->ptr + bkt->size) break; } assert(bkt != nullptr && "failed to determine correct memory bucket"); pk_delete_bkt(ptr, sz, bkt); } void pk_delete(const void* ptr, size_t sz, struct pk_membucket* bkt) { if (bkt != NULL) return pk_delete_bkt(ptr, sz, bkt); return pk_delete_base(ptr, sz); } #endif /* PK_IMPL_MEM */ #ifndef PK_STR_H #define PK_STR_H #include struct pk_str { char *val; uint32_t length; uint32_t reserved; }; struct pk_cstr { const char *val; uint32_t length; uint32_t reserved; }; struct pk_str cstring_to_pk_str(char *s); struct pk_cstr cstring_to_pk_cstr(const char *s); struct pk_str pk_cstr_to_pk_str(const struct pk_cstr *s); struct pk_cstr pk_str_to_pk_cstr(const struct pk_str *s); int pk_compare_str(const struct pk_str *lhs, const struct pk_str *rhs); int pk_compare_cstr(const struct pk_cstr *lhs, const struct pk_cstr *rhs); #endif /* PK_STR_H */ #ifdef PK_IMPL_STR #include struct pk_str cstring_to_pk_str(char *s) { return (struct pk_str) { .val = s, .length = (uint32_t)(strlen(s)), .reserved = 0, }; } struct pk_cstr cstring_to_pk_cstr(const char *s) { return (struct pk_cstr) { .val = s, .length = (uint32_t)(strlen(s)), .reserved = 0, }; } struct pk_str pk_cstr_to_pk_str(const struct pk_cstr *s) { return (struct pk_str) { .val = (char *)(s->val), .length = s->length, .reserved = s->reserved, }; } struct pk_cstr pk_str_to_pk_cstr(const struct pk_str *s) { return (struct pk_cstr) { .val = (char *)(s->val), .length = s->length, .reserved = s->reserved, }; } int pk_compare_str(const struct pk_str *lhs, const struct pk_str *rhs) { return strncmp(lhs->val, rhs->val, PK_MIN(lhs->length, rhs->length)); } int pk_compare_cstr(const struct pk_cstr *lhs, const struct pk_cstr *rhs) { return strncmp(lhs->val, rhs->val, PK_MIN(lhs->length, rhs->length)); } #endif /* PK_IMPL_STR */ #ifndef PK_EV_H #define PK_EV_H #include typedef uint64_t pk_ev_mgr_id_T; typedef uint64_t pk_ev_id_T; // note: pk_ev_init() is NOT thread-safe void pk_ev_init(); // note: pk_ev_teardown() is NOT thread-safe void pk_ev_teardown(); const pk_ev_mgr_id_T pk_ev_create_mgr(); void pk_ev_destroy_mgr(pk_ev_mgr_id_T evmgr); typedef void (pk_ev_cb_fn)(void *user_event_data, void *user_cb_data, void *user_ev_data); const pk_ev_id_T pk_ev_register_ev(pk_ev_mgr_id_T evmgr, void *user_ev_data); bool pk_ev_register_cb(pk_ev_mgr_id_T evmgr, pk_ev_id_T evid, pk_ev_cb_fn *cb, void *user_cb_data); void pk_ev_emit(pk_ev_mgr_id_T evmgr, pk_ev_id_T evid, void *user_emit_data); #endif /* PK_EV_H */ #ifdef PK_IMPL_EV #include #include #include #include #include #include #include #ifndef PK_EV_INIT_MGR_COUNT # define PK_EV_INIT_MGR_COUNT 1 #endif #ifndef PK_EV_INIT_EV_COUNT # define PK_EV_INIT_EV_COUNT 16 #endif #ifndef PK_EV_INIT_CB_COUNT # define PK_EV_INIT_CB_COUNT 8 #endif #ifndef PK_EV_GROW_RATIO # define PK_EV_GROW_RATIO 1.5 #endif struct pk_ev_cb { pk_ev_cb_fn *cb; void *user_cb_data; }; struct pk_ev { struct pk_ev_cb *ev_cbs; void *user_ev_data; atomic_uint_fast8_t n_ev_cbs; }; struct pk_ev_mgr { struct pk_ev *ev; atomic_uint_fast8_t n_ev; atomic_uint_fast8_t rn_ev; atomic_uint_fast8_t rn_cb; }; struct pk_ev_mstr { atomic_uint_fast64_t flg_mgrs; atomic_uint_fast64_t rn_mgrs; struct pk_ev_mgr **mgrs; mtx_t *mtxs; }; struct pk_ev_mstr pk_ev_mstr; void pk_ev_init() { int i; pk_ev_mstr.mgrs = (struct pk_ev_mgr **)malloc(sizeof(void *) * PK_EV_INIT_MGR_COUNT); pk_ev_mstr.mtxs = (mtx_t*)malloc(sizeof(mtx_t) * PK_EV_INIT_MGR_COUNT); memset(pk_ev_mstr.mgrs, 0, sizeof(void *) * PK_EV_INIT_MGR_COUNT); memset(pk_ev_mstr.mtxs, 0, sizeof(mtx_t) * PK_EV_INIT_MGR_COUNT); for (i = 0; i < PK_EV_INIT_MGR_COUNT; ++i) { mtx_init(&pk_ev_mstr.mtxs[i], mtx_plain); } atomic_store(&pk_ev_mstr.flg_mgrs, 0); atomic_store(&pk_ev_mstr.rn_mgrs, PK_EV_INIT_MGR_COUNT); } void pk_ev_teardown() { int i; for (i = 0; i < pk_ev_mstr.rn_mgrs; ++i) { if ((atomic_load(&pk_ev_mstr.rn_mgrs) & (1lu << i)) == 0) continue; mtx_lock(&pk_ev_mstr.mtxs[i]); free(pk_ev_mstr.mgrs[i]); pk_ev_mstr.mgrs[i] = NULL; mtx_unlock(&pk_ev_mstr.mtxs[i]); mtx_destroy(&pk_ev_mstr.mtxs[i]); } free(pk_ev_mstr.mgrs); free(pk_ev_mstr.mtxs); pk_ev_mstr.mgrs = NULL; pk_ev_mstr.mtxs = NULL; } static struct pk_ev_mgr* pk_ev_inner_ev_mgr_create(uint64_t ev_count, uint64_t cb_count) { assert(ev_count < 0x100); assert(cb_count < 0x100); int i; struct pk_ev *ev; size_t sz = sizeof(struct pk_ev_mgr) + ((sizeof(struct pk_ev) * ev_count)) + (sizeof (struct pk_ev_cb) * ev_count * cb_count); size_t sz_ev = (sizeof(struct pk_ev_cb) * cb_count); size_t sz_evs = sizeof(struct pk_ev) * ev_count; struct pk_ev_mgr *mgr = (struct pk_ev_mgr*)malloc(sz); if (mgr == NULL) goto early_exit; memset(mgr, 0, sz); mgr->ev = (struct pk_ev*)(((char *)mgr) + sizeof(struct pk_ev_mgr)); atomic_init(&mgr->rn_ev, ev_count); atomic_init(&mgr->rn_cb, cb_count); atomic_init(&mgr->n_ev, 0); for (i = 0; i < mgr->rn_ev; ++i) { ev = &mgr->ev[i]; atomic_init(&ev->n_ev_cbs, 0); ev->ev_cbs = (struct pk_ev_cb*)(((char *)mgr) + sizeof(struct pk_ev_mgr) + sz_evs + (sz_ev * i)); } early_exit: return mgr; } static void pk_ev_inner_ev_mgr_clone(struct pk_ev_mgr *old, struct pk_ev_mgr *mgr) { int i; struct pk_ev *ev_old; struct pk_ev *ev; atomic_store(&mgr->n_ev, atomic_load(&old->n_ev)); for (i = 0; i < old->n_ev; ++i) { ev_old = &old->ev[i]; ev = &mgr->ev[i]; memcpy(ev->ev_cbs, ev_old->ev_cbs, sizeof(struct pk_ev_cb) * atomic_load(&ev_old->n_ev_cbs)); atomic_store(&ev->n_ev_cbs, atomic_load(&ev_old->n_ev_cbs)); } } const pk_ev_mgr_id_T pk_ev_create_mgr() { uint64_t i; pk_ev_mgr_id_T flg; pk_ev_mgr_id_T flg_new; pk_ev_mgr_id_T id; struct pk_ev_mgr *mgr = pk_ev_inner_ev_mgr_create(PK_EV_INIT_EV_COUNT, PK_EV_INIT_CB_COUNT); if (mgr == NULL) return -1; start: flg = atomic_load(&pk_ev_mstr.flg_mgrs); while (1) { flg_new = flg; for (i = 0; i < atomic_load(&pk_ev_mstr.rn_mgrs); ++i) { if ((flg & (1u << i)) == 0) break; } if (i == atomic_load(&pk_ev_mstr.rn_mgrs)) { goto recreate; } id = i; flg_new |= (1u << i); if (atomic_compare_exchange_strong(&pk_ev_mstr.flg_mgrs, &flg, flg_new)) break; thrd_yield(); } pk_ev_mstr.mgrs[id]= mgr; return id; recreate: // TODO recreate mgr, out of space assert(1 == 0 && "[pkev.h] Out of mgr space."); exit(1); goto start; } void pk_ev_destroy_mgr(pk_ev_mgr_id_T evmgr) { assert(evmgr >= 0); mtx_lock(&pk_ev_mstr.mtxs[evmgr]); free(pk_ev_mstr.mgrs[evmgr]); pk_ev_mstr.mgrs[evmgr] = NULL; mtx_unlock(&pk_ev_mstr.mtxs[evmgr]); } const pk_ev_id_T pk_ev_register_ev(pk_ev_mgr_id_T evmgr, void *user_ev_data) { assert(evmgr < 64); uint64_t new_size; pk_ev_id_T id; struct pk_ev_mgr *mgr; mtx_lock(&pk_ev_mstr.mtxs[evmgr]); if (pk_ev_mstr.mgrs[evmgr]->n_ev == pk_ev_mstr.mgrs[evmgr]->rn_ev) { new_size = PK_MAX(2, PK_MIN(255, pk_ev_mstr.mgrs[evmgr]->rn_ev * PK_EV_GROW_RATIO)); if (new_size == pk_ev_mstr.mgrs[evmgr]->rn_ev) { PK_LOG_ERR("[pkev.h] need more room, but failed to grow ev count.\n"); mtx_unlock(&pk_ev_mstr.mtxs[evmgr]); exit(1); } mgr = pk_ev_inner_ev_mgr_create(new_size, pk_ev_mstr.mgrs[evmgr]->rn_cb); pk_ev_inner_ev_mgr_clone(pk_ev_mstr.mgrs[evmgr], mgr); free(pk_ev_mstr.mgrs[evmgr]); pk_ev_mstr.mgrs[evmgr] = mgr; } id = pk_ev_mstr.mgrs[evmgr]->n_ev++; pk_ev_mstr.mgrs[evmgr]->ev[id].user_ev_data = user_ev_data; mtx_unlock(&pk_ev_mstr.mtxs[evmgr]); return id; } bool pk_ev_register_cb(pk_ev_mgr_id_T evmgr, pk_ev_id_T evid, pk_ev_cb_fn *cb, void *user_cb_data) { assert(evmgr < 64); uint64_t new_size; struct pk_ev_mgr *mgr; uint8_t cb_index; mtx_lock(&pk_ev_mstr.mtxs[evmgr]); if (pk_ev_mstr.mgrs[evmgr]->ev[evid].n_ev_cbs == pk_ev_mstr.mgrs[evmgr]->rn_cb) { new_size = PK_MAX(2, PK_MIN(255, pk_ev_mstr.mgrs[evmgr]->rn_cb * PK_EV_GROW_RATIO)); if (new_size == pk_ev_mstr.mgrs[evmgr]->rn_cb) { PK_LOG_ERR("[pkev.h] need more room, but failed to grow cb count.\n"); mtx_unlock(&pk_ev_mstr.mtxs[evmgr]); exit(1); } mgr = pk_ev_inner_ev_mgr_create(pk_ev_mstr.mgrs[evmgr]->rn_ev, new_size); pk_ev_inner_ev_mgr_clone(pk_ev_mstr.mgrs[evmgr], mgr); free(pk_ev_mstr.mgrs[evmgr]); pk_ev_mstr.mgrs[evmgr] = mgr; } cb_index = pk_ev_mstr.mgrs[evmgr]->ev[evid].n_ev_cbs++; pk_ev_mstr.mgrs[evmgr]->ev[evid].ev_cbs[cb_index].cb = cb; pk_ev_mstr.mgrs[evmgr]->ev[evid].ev_cbs[cb_index].user_cb_data = user_cb_data; mtx_unlock(&pk_ev_mstr.mtxs[evmgr]); return true; } void pk_ev_emit(pk_ev_mgr_id_T evmgr, pk_ev_id_T evid, void *user_emit_data) { assert(evmgr < 64); uint8_t i; for (i = 0; i < pk_ev_mstr.mgrs[evmgr]->ev[evid].n_ev_cbs; ++i) { (*pk_ev_mstr.mgrs[evmgr]->ev[evid].ev_cbs[i].cb)( pk_ev_mstr.mgrs[evmgr]->ev[evid].user_ev_data, pk_ev_mstr.mgrs[evmgr]->ev[evid].ev_cbs[i].user_cb_data, user_emit_data); } } #endif /* PK_IMPL_EV */ #ifndef PK_PKARR_H #define PK_PKARR_H #include struct pk_arr { uint32_t next; uint32_t reserved; uint32_t stride; uint32_t alignment; struct pk_membucket *bkt; void *data; }; typedef bool(pk_arr_item_compare)(void *user_data, void *item); void pk_arr_clear(struct pk_arr *arr); void pk_arr_reset(struct pk_arr *arr); void pk_arr_reserve(struct pk_arr *arr, uint32_t count); void pk_arr_resize(struct pk_arr *arr, uint32_t count); void pk_arr_move_to_back(struct pk_arr *arr, uint32_t index); void pk_arr_append(struct pk_arr *arr, void *data); void pk_arr_remove_at(struct pk_arr *arr, uint32_t index); uint32_t pk_arr_find_first_index(struct pk_arr *arr, void *user_data, pk_arr_item_compare *fn); #endif /* PK_PKARR_H */ #ifdef PK_IMPL_ARR #ifndef PK_ARR_GROW_RATIO #define PK_ARR_GROW_RATIO 1.5 #endif #ifndef PK_ARR_INITIAL_COUNT #define PK_ARR_INITIAL_COUNT 16 #endif void pk_arr_clear(struct pk_arr *arr) { arr->next = 0; } void pk_arr_reset(struct pk_arr *arr) { if (arr->data != NULL) pk_delete(arr->data, arr->stride * arr->reserved, arr->bkt); arr->data = NULL; arr->next = 0; arr->reserved = 0; } void pk_arr_reserve(struct pk_arr *arr, uint32_t count) { if (arr->reserved >= count) return; void *new_data = pk_new(arr->stride * count, arr->alignment, arr->bkt); if (arr->data != NULL) { if (arr->next != 0) { memcpy(new_data, arr->data, arr->stride * arr->reserved); } pk_delete(arr->data, arr->stride * arr->reserved, arr->bkt); } arr->reserved = count; arr->data = new_data; } void pk_arr_resize(struct pk_arr *arr, uint32_t count) { pk_arr_reserve(arr, count); arr->next = count; } void pk_arr_move_to_back(struct pk_arr *arr, uint32_t index) { if (arr->reserved == 0) return; if (arr->next <= 1) return; #ifdef PK_ARR_MOVE_IN_PLACE uint32_t i, ii; uint8_t *target = (uint8_t *)pk_new(arr->stride, arr->alignment, arr->bkt); uint8_t *buffer = (uint8_t *)arr->data; for (ii = 0, i = arr->stride * index; ii < arr->stride; ++ii, ++i) { target[ii] = buffer[i]; } for (i = arr->stride * index; i < (arr->stride * (arr->next - 1)); ++i) { buffer[i] = buffer[i + arr->stride]; } for (ii = 0, i = arr->stride * (arr->next - 1); ii < arr->stride; ++ii, ++i) { buffer[i] = target[ii]; } pk_delete(target, arr->stride, arr->bkt); #else char *new_data = (char *)pk_new(arr->stride * arr->reserved, arr->alignment, arr->bkt); if (index > 0) { memcpy(new_data, arr->data, arr->stride * index); } memcpy( new_data + (arr->stride * (arr->next - 1)), ((char *)arr->data) + (arr->stride * index), arr->stride); memcpy( new_data + (arr->stride * index), ((char *)arr->data) + (arr->stride * (index + 1)), arr->stride * (arr->next - index - 1)); pk_delete(arr->data, arr->stride * arr->reserved, arr->bkt); arr->data = (void *)new_data; #endif } void pk_arr_append(struct pk_arr *arr, void *data) { if (arr->reserved == arr->next) { uint32_t new_count = PK_MAX(arr->reserved == 0 ? PK_ARR_INITIAL_COUNT : arr->reserved * PK_ARR_GROW_RATIO, arr->reserved + 1); void *new_data = pk_new(arr->stride * new_count, arr->alignment, arr->bkt); if (arr->data != NULL) { memcpy(new_data, arr->data, arr->stride * arr->reserved); pk_delete(arr->data, arr->stride * arr->reserved, arr->bkt); } arr->data = new_data; arr->reserved = new_count; } memcpy(((char *)arr->data) + (arr->stride * arr->next), data, arr->stride); arr->next += 1; return; } void pk_arr_remove_at(struct pk_arr *arr, uint32_t index) { if (arr->reserved == 0) return; if (index == arr->next - 1) { arr->next -=1; return; } #ifdef PK_ARR_MOVE_IN_PLACE uint32_t i; uint8_t *buffer = (uint8_t *)arr->data; for (i = arr->stride * index; i < (arr->stride * (arr->next - 1)); ++i) { buffer[i] = buffer[i + arr->stride]; } #else char *new_data = (char *)pk_new(arr->stride * arr->reserved, arr->alignment, arr->bkt); if (index > 0) { memcpy(new_data, arr->data, arr->stride * index); } memcpy( new_data + (arr->stride * index), ((char *)arr->data) + (arr->stride * (index + 1)), arr->stride * (arr->next - index - 1)); pk_delete(arr->data, arr->stride * arr->reserved, arr->bkt); arr->data = (void *)new_data; #endif arr->next -= 1; } uint32_t pk_arr_find_first_index(struct pk_arr *arr, void *user_data, pk_arr_item_compare *fn) { uint32_t i; char *char_data = (char *)arr->data; for (i = 0; i < arr->next; ++i) { if (fn(user_data, char_data + (arr->stride * i))) return i; } return -1; } #endif /* PK_IMPL_ARR */ #ifndef PK_PK_STN_H #define PK_PK_STN_H #include #include #include #include #include enum PK_STN_RES { PK_STN_RES_SUCCESS, PK_STN_RES_OVERFLOW, PK_STN_RES_UNDERFLOW, PK_STN_RES_INCONVERTIBLE }; enum PK_STN_RES pk_stn_int64_t(int64_t *i, char const *s, int base); enum PK_STN_RES pk_stn_uint64_t(uint64_t *i, char const *s, int base); enum PK_STN_RES pk_stn_int32_t(int32_t *i, char const *s, int base); enum PK_STN_RES pk_stn_uint32_t(uint32_t *i, char const *s, int base); enum PK_STN_RES pk_stn_int16_t(int16_t *i, char const *s, int base); enum PK_STN_RES pk_stn_uint16_t(uint16_t *i, char const *s, int base); enum PK_STN_RES pk_stn_int8_t(int8_t *i, char const *s, int base); enum PK_STN_RES pk_stn_uint8_t(uint8_t *i, char const *s, int base); enum PK_STN_RES pk_stn_float(float *f, char const *s); enum PK_STN_RES pk_stn_double(double *d, char const *s); enum PK_STN_RES pk_stn_float_e(float *f, char const *s, char **pEnd); enum PK_STN_RES pk_stn_double_e(double *d, char const *s, char **pEnd); #if defined(__cplusplus) template enum PK_STN_RES pk_stn(T *n, char const *s, int base = 0) { if constexpr(std::is_same::value) { return pk_stn_int64_t(n, s, base); } if constexpr(std::is_same::value) { return pk_stn_uint64_t(n, s, base); } if constexpr(std::is_same::value) { return pk_stn_int32_t(n, s, base); } if constexpr(std::is_same::value) { return pk_stn_uint32_t(n, s, base); } if constexpr(std::is_same::value) { return pk_stn_int16_t(n, s, base); } if constexpr(std::is_same::value) { return pk_stn_uint16_t(n, s, base); } if constexpr(std::is_same::value) { return pk_stn_int8_t(n, s, base); } if constexpr(std::is_same::value) { return pk_stn_uint8_t(n, s, base); } if constexpr(std::is_same::value) { return pk_stn_float(n, s); } if constexpr(std::is_same::value) { return pk_stn_double(n, s); } return (PK_STN_RES)-1; } #endif /* defined(__cplusplus) */ #endif /* PK_PK_STN_H */ #ifdef PK_IMPL_STN enum PK_STN_RES pk_stn_int64_t(int64_t *i, char const *s, int base) { char *end; long long l; errno = 0; l = strtoll(s, &end, base); if (errno == ERANGE) { if (l == LLONG_MAX) return PK_STN_RES_OVERFLOW; return PK_STN_RES_UNDERFLOW; } if (*s == '\0' || *end != '\0') { return PK_STN_RES_INCONVERTIBLE; } *i = l; return PK_STN_RES_SUCCESS; } enum PK_STN_RES pk_stn_uint64_t(uint64_t *i, char const *s, int base) { char *end; unsigned long long l; errno = 0; l = strtoull(s, &end, base); if (errno == ERANGE) { if (l == ULLONG_MAX) return PK_STN_RES_OVERFLOW; return PK_STN_RES_UNDERFLOW; } if (*s == '\0' || *end != '\0') { return PK_STN_RES_INCONVERTIBLE; } *i = l; return PK_STN_RES_SUCCESS; } enum PK_STN_RES pk_stn_int32_t(int32_t *i, char const *s, int base) { char *end; long l; errno = 0; l = strtol(s, &end, base); if (errno == ERANGE) { if (l == LONG_MAX) return PK_STN_RES_OVERFLOW; return PK_STN_RES_UNDERFLOW; } if (*s == '\0' || *end != '\0') { return PK_STN_RES_INCONVERTIBLE; } *i = l; return PK_STN_RES_SUCCESS; } enum PK_STN_RES pk_stn_uint32_t(uint32_t *i, char const *s, int base) { char *end; unsigned long l; errno = 0; l = strtoul(s, &end, base); if (errno == ERANGE) { if (l == ULONG_MAX) return PK_STN_RES_OVERFLOW; return PK_STN_RES_UNDERFLOW; } if (*s == '\0' || *end != '\0') { return PK_STN_RES_INCONVERTIBLE; } *i = l; return PK_STN_RES_SUCCESS; } enum PK_STN_RES pk_stn_int16_t(int16_t *i, char const *s, int base) { char *end; long l; errno = 0; l = strtol(s, &end, base); if (errno == ERANGE) { if (l == LONG_MAX) return PK_STN_RES_OVERFLOW; return PK_STN_RES_UNDERFLOW; } if (*s == '\0' || *end != '\0') { return PK_STN_RES_INCONVERTIBLE; } *i = l; return PK_STN_RES_SUCCESS; } enum PK_STN_RES pk_stn_uint16_t(uint16_t *i, char const *s, int base) { char *end; unsigned long l; errno = 0; l = strtoul(s, &end, base); if (errno == ERANGE) { if (l == ULONG_MAX) return PK_STN_RES_OVERFLOW; return PK_STN_RES_UNDERFLOW; } if (*s == '\0' || *end != '\0') { return PK_STN_RES_INCONVERTIBLE; } *i = l; return PK_STN_RES_SUCCESS; } enum PK_STN_RES pk_stn_int8_t(int8_t *i, char const *s, int base) { char *end; long l; errno = 0; l = strtol(s, &end, base); if (errno == ERANGE) { if (l == LONG_MAX) return PK_STN_RES_OVERFLOW; return PK_STN_RES_UNDERFLOW; } if (*s == '\0' || *end != '\0') { return PK_STN_RES_INCONVERTIBLE; } *i = l; return PK_STN_RES_SUCCESS; } enum PK_STN_RES pk_stn_uint8_t(uint8_t *i, char const *s, int base) { char *end; unsigned long l; errno = 0; l = strtoul(s, &end, base); if (errno == ERANGE) { if (l == ULONG_MAX) return PK_STN_RES_OVERFLOW; return PK_STN_RES_UNDERFLOW; } if (*s == '\0' || *end != '\0') { return PK_STN_RES_INCONVERTIBLE; } *i = l; return PK_STN_RES_SUCCESS; } enum PK_STN_RES pk_stn_float(float *f, char const *s) { char *end; return pk_stn_float_e(f, s, &end); } enum PK_STN_RES pk_stn_double(double *d, char const *s) { char *end; return pk_stn_double_e(d, s, &end); } enum PK_STN_RES pk_stn_float_e(float *f, char const *s, char **pEnd) { float l; errno = 0; l = strtof(s, pEnd); if (errno == ERANGE && l == HUGE_VALF) { return PK_STN_RES_OVERFLOW; } if (errno == ERANGE && l == -HUGE_VALF) { return PK_STN_RES_UNDERFLOW; } if (*s == '\0' || &s == (const char **)pEnd) { return PK_STN_RES_INCONVERTIBLE; } *f = l; return PK_STN_RES_SUCCESS; } enum PK_STN_RES pk_stn_double_e(double *d, char const *s, char **pEnd) { double l; errno = 0; l = strtod(s, pEnd); if (errno == ERANGE && l == HUGE_VAL) { return PK_STN_RES_OVERFLOW; } if (errno == ERANGE && l == -HUGE_VAL) { return PK_STN_RES_UNDERFLOW; } if (*s == '\0' || &s == (const char **)pEnd) { return PK_STN_RES_INCONVERTIBLE; } *d = l; return PK_STN_RES_SUCCESS; } #endif /* PK_IMPL_STN */ #ifndef PK_PKTMR_H #define PK_PKTMR_H #include /* 2024-12-17 JCB * I have read that in more recent Linux kernels, _MONOTONIC and _REALTIME * do not require syscalls, while all of the other calls can. * In testing on my personal machine, this seems to hold true. Using * CLOCK_PROCESS_CPUTIME_ID consistently elapsed thousands of nanoseconds, * even with no work between sequential _start() and _stop() calls. * Meanwhile, the same test with _MONOTONIC elapsed only tens of nanoseconds. * Consider replacing explicit usage with a define for more user control. */ /* struct pk_tmr */ struct pk_tmr { struct timespec b; // begin struct timespec e; // end }; #define pk_tmr_start(tmr) { clock_gettime(CLOCK_MONOTONIC, &tmr.b); } #define pk_tmr_stop(tmr) { clock_gettime(CLOCK_MONOTONIC, &tmr.e); } #define pk_tmr_duration_double(tmr) ((1000.0 * tmr.e.tv_sec + 1e-6 * tmr.e.tv_nsec) - (1000.0 * tmr.b.tv_sec + 1e-6 * tmr.b.tv_nsec)) #define pk_tmr_duration_nano(tmr) ((((uint64_t)tmr.e.tv_sec * (uint64_t)1000000000) + tmr.e.tv_nsec) - (((uint64_t)tmr.b.tv_sec * (uint64_t)1000000000) + (uint64_t)tmr.b.tv_nsec)) #endif /* PK_PKTMR_H */ #endif /* PK_SINGLE_HEADER_FILE_H */