From 8d7bfe0bc901e01f1c8892063e04e4f0c2de1624 Mon Sep 17 00:00:00 2001 From: Riley King-Saunders Date: Sun, 22 Jun 2025 03:02:42 +1000 Subject: [PATCH] Buildsystem: Removed include/basalt_window.h. See prior commit #cac50b01 Library: Added logging and memory modules. The logging module provides a flexible interface for adding and removing logging streams (think stdout, stderr, a file) and supports ANSI colouring and including the date and time of each log call. Its header also provides several definitions for assertions, each corresponding to a specific log level and some other side effects such as debug_break(), returning, exiting or doing nothing. The memory module wraps (_)aligned_alloc and free functions and includes memory tagging by class and location and the ability to fetch a formatted string containing this information. This can allow you to narrow down where memory leaks and double frees occur to particular sections within a module. Memory can be aligned to any, 32, 64 or 4096 byte boundaries using this module. --- include/basalt_window.h | 29 ----- include/core/basalt_logger.h | 94 ++++++++++++++ include/core/basalt_memory.h | 86 +++++++++++++ src/core/basalt_logger.cpp | 185 +++++++++++++++++++++++++++ src/core/basalt_memory.cpp | 235 +++++++++++++++++++++++++++++++++++ 5 files changed, 600 insertions(+), 29 deletions(-) delete mode 100644 include/basalt_window.h create mode 100644 include/core/basalt_logger.h create mode 100644 include/core/basalt_memory.h create mode 100644 src/core/basalt_logger.cpp create mode 100644 src/core/basalt_memory.cpp diff --git a/include/basalt_window.h b/include/basalt_window.h deleted file mode 100644 index 9eb07eb..0000000 --- a/include/basalt_window.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#define GLFW_INCLUDE_VULKAN -#include "GLFW/glfw3.h" - -namespace basalt -{ - class Window - { - public: - Window(const Window& src) = delete; - Window& operator =(const Window& src) = delete; - Window(Window&& other) noexcept; - Window& operator =(Window&& other) noexcept; - - Window(uint16_t width, uint16_t height, const char* title); - ~Window(void) noexcept; - - void swap(Window& other); - operator GLFWwindow* (void) const noexcept; - - const bool should_close(void) const noexcept; - - protected: - GLFWwindow* window = nullptr; - const char* window_title = "N/A"; - uint16_t width = 0; - uint16_t height = 0; - }; -} \ No newline at end of file diff --git a/include/core/basalt_logger.h b/include/core/basalt_logger.h new file mode 100644 index 0000000..29522d9 --- /dev/null +++ b/include/core/basalt_logger.h @@ -0,0 +1,94 @@ +#pragma once +#include "basalt_defines.h" +#include +#include + +#ifndef BASALT_LOGGER_MAX_STREAMS +#define BASALT_LOGGER_MAX_STREAMS 7 +#endif + +typedef enum LOG_LEVEL { + LOG_FATAL = 1, + LOG_ERROR = 2, + LOG_WARN = 4, + LOG_INFO = 8, + LOG_OK = 16, + LOG_WEIRD = 32, + LOG_DEBUG = 64, + LOG_TRACE = 128, + + LOG_MASK_ERRORS = LOG_FATAL | LOG_ERROR, + LOG_MASK_DEBUGGING = LOG_WEIRD | LOG_DEBUG | LOG_TRACE, + LOG_MASK_INFO = LOG_WARN | LOG_INFO | LOG_OK, + LOG_MASK_ALL = LOG_MASK_ERRORS | LOG_MASK_DEBUGGING | LOG_MASK_INFO, + LOG_MASK_DEFAULT = LOG_MASK_ERRORS | LOG_MASK_INFO, +} LOG_LEVEL; + +typedef enum LOG_STREAM_FLAGS { + // Ignore this flag - it is internal to the library + LOG_STREAM_FLAG_INTERNAL_ALLOCATED_BIT = 1, + LOG_STREAM_FLAG_SUPPORTS_ANSI_BIT = 2, + LOG_STREAM_FLAG_INCLUDE_DATETIME_BIT = 4, + LOG_STREAM_FLAG_DISABLE_PREFIXES = 8, +} LOG_STREAM_BIT_FLAGS; + +typedef struct logger_stream_t { + u64 stream : 48; + u64 mask : 8 ; + u64 flags : 8 ; +} logger_stream_t; + +void initialize_logger(); +void terminate_logger (); + +FILE* logger_stream_get_file(logger_stream_t* state); +u8 logger_add_stream(FILE* output, LOG_LEVEL mask, LOG_STREAM_FLAGS flags); +void logger_remove_stream(u8 index); +u8 logger_find_stream(FILE* target); + +void basalt_log(const LOG_LEVEL level, const char* msg, ...); +void basalt_write(const LOG_LEVEL level, const char* msg, ...); + +#define BFATAL(msg, ...) basalt_log(LOG_FATAL, msg, ##__VA_ARGS__); +#define BERROR(msg, ...) basalt_log(LOG_ERROR, msg, ##__VA_ARGS__); +#define BWARN(msg, ...) basalt_log(LOG_WARN, msg, ##__VA_ARGS__); +#define BINFO(msg, ...) basalt_log(LOG_INFO, msg, ##__VA_ARGS__); +#define BOK(msg, ...) basalt_log(LOG_OK, msg, ##__VA_ARGS__); +#define BWEIRD(msg, ...) basalt_log(LOG_WEIRD, msg, ##__VA_ARGS__); +#define BDEBUG(msg, ...) basalt_log(LOG_DEBUG, msg, ##__VA_ARGS__); +#define BTRACE(msg, ...) basalt_log(LOG_TRACE, msg, ##__VA_ARGS__); + +#define BASSERT_FATAL(cond, msg, ...) \ + if (cond){} \ + else { \ + BFATAL(msg, #cond, __FILE__, __LINE__, ##__VA_ARGS__); \ + debug_break(); \ + exit(-1); \ + } +#define BASSERT_ERROR(cond, ret, msg, ...) \ + if (cond){} \ + else { \ + BERROR(msg, #cond, __FILE__, __LINE__, ##__VA_ARGS__); \ + ret \ + } +#define BASSERT_WARN(cond, msg, ...) \ + if (cond){} \ + else { \ + BWARN(msg, #cond, __FILE__, __LINE__, ##__VA_ARGS__); \ + } +#ifdef BDEBUG +#define BASSERT_WEIRD(cond, msg, ...) \ + if (cond){} \ + else { \ + BWEIRD(msg, #cond, __FILE__, __LINE__, ##__VA_ARGS__); \ + } +#define BASSERT_DEBUG(cond, msg, ...) \ + if (cond){} \ + else { \ + BFATAL(msg, #cond, __FILE__, __LINE__, ##__VA_ARGS__); \ + debug_break(); \ + } +#else +#define BASSERT_WEIRD(cond, msg, ...) +#define BASSERT_DEBUG(cond, msg, ...) +#endif \ No newline at end of file diff --git a/include/core/basalt_memory.h b/include/core/basalt_memory.h new file mode 100644 index 0000000..d5d79cf --- /dev/null +++ b/include/core/basalt_memory.h @@ -0,0 +1,86 @@ +#pragma once +#include "basalt_defines.h" + +typedef u16 MEMORY_TAG; +typedef enum MEMORY_TAGS { + MEMORY_TAG_BITWIDTH_CLASS = 5, + MEMORY_TAG_BITWIDTH_ZONE = 4, + MEMORY_TAG_BITWIDTH_ALIGN = 2, + MEMORY_TAG_SHIFT_CLASS = 0, + MEMORY_TAG_SHIFT_ZONE = MEMORY_TAG_SHIFT_CLASS + MEMORY_TAG_BITWIDTH_CLASS, + MEMORY_TAG_SHIFT_ALIGN = MEMORY_TAG_SHIFT_ZONE + MEMORY_TAG_BITWIDTH_ZONE, + MEMORY_TAG_MASK_CLASS = ((1 << MEMORY_TAG_BITWIDTH_CLASS) - 1) << MEMORY_TAG_SHIFT_CLASS, + MEMORY_TAG_MASK_ZONE = ((1 << MEMORY_TAG_BITWIDTH_ZONE) - 1) << MEMORY_TAG_SHIFT_ZONE, + MEMORY_TAG_MASK_ALIGN = ((1 << MEMORY_TAG_BITWIDTH_ALIGN) - 1) << MEMORY_TAG_SHIFT_ALIGN, + + MEMORY_TAG_CLASS_UNKNOWN = 0 << MEMORY_TAG_SHIFT_CLASS, + MEMORY_TAG_CLASS_ARRAY = 1 << MEMORY_TAG_SHIFT_CLASS, + MEMORY_TAG_CLASS_DYNARRAY = 2 << MEMORY_TAG_SHIFT_CLASS, + MEMORY_TAG_CLASS_STRING = 3 << MEMORY_TAG_SHIFT_CLASS, + MEMORY_TAG_CLASS_CIRCULAR_BUFFER = 4 << MEMORY_TAG_SHIFT_CLASS, + MEMORY_TAG_CLASS_DICT = 5 << MEMORY_TAG_SHIFT_CLASS, + MEMORY_TAG_CLASS_BINTREE = 6 << MEMORY_TAG_SHIFT_CLASS, + MEMORY_TAG_CLASS_OCTTREE = 7 << MEMORY_TAG_SHIFT_CLASS, + MEMORY_TAG_CLASS_TEXTURE = 9 << MEMORY_TAG_SHIFT_CLASS, + MEMORY_TAG_CLASS_JOB = 9 << MEMORY_TAG_SHIFT_CLASS, + MEMORY_TAG_CLASS_TRANSFORM = 10 << MEMORY_TAG_SHIFT_CLASS, + MEMORY_TAG_CLASS_RENDER_OBJECT = 11 << MEMORY_TAG_SHIFT_CLASS, + MEMORY_TAG_CLASS_MATERIAL = 12 << MEMORY_TAG_SHIFT_CLASS, + MEMORY_TAG_CLASS_MAX_BUILTIN = (MEMORY_TAG_CLASS_MATERIAL >> MEMORY_TAG_SHIFT_CLASS) + 1, + MEMORY_TAG_CLASS_MAX = (1 << MEMORY_TAG_BITWIDTH_CLASS) - 1, + + MEMORY_TAG_ZONE_UNKNOWN = 0 << MEMORY_TAG_SHIFT_ZONE, + MEMORY_TAG_ZONE_SCENE = 1 << MEMORY_TAG_SHIFT_ZONE, + MEMORY_TAG_ZONE_APPLICATION = 2 << MEMORY_TAG_SHIFT_ZONE, + MEMORY_TAG_ZONE_RENDERER = 3 << MEMORY_TAG_SHIFT_ZONE, + MEMORY_TAG_ZONE_ENGINE = 4 << MEMORY_TAG_SHIFT_ZONE, + MEMORY_TAG_ZONE_EVENT = 5 << MEMORY_TAG_SHIFT_ZONE, + MEMORY_TAG_ZONE_DEBUG = 6 << MEMORY_TAG_SHIFT_ZONE, + MEMORY_TAG_ZONE_AUDIO = 7 << MEMORY_TAG_SHIFT_ZONE, + MEMORY_TAG_ZONE_MAX_BUILTIN = (MEMORY_TAG_ZONE_AUDIO >> MEMORY_TAG_SHIFT_ZONE) + 1, + MEMORY_TAG_ZONE_MAX = (1 << MEMORY_TAG_BITWIDTH_ZONE) - 1, + + MEMORY_TAG_ALIGN_ANY = 0 << MEMORY_TAG_SHIFT_ALIGN, + MEMORY_TAG_ALIGN_32 = 1 << MEMORY_TAG_SHIFT_ALIGN, + MEMORY_TAG_ALIGN_64 = 2 << MEMORY_TAG_SHIFT_ALIGN, + MEMORY_TAG_ALIGN_PAGE = 3 << MEMORY_TAG_SHIFT_ALIGN, + MEMORY_TAG_ALIGN_MAX_BUILTIN = (MEMORY_TAG_ALIGN_PAGE >> MEMORY_TAG_SHIFT_ALIGN) + 1, + MEMORY_TAG_ALIGN_MAX = (1 << MEMORY_TAG_BITWIDTH_ALIGN) - 1, +} MEMORY_TAGS; + +namespace basalt +{ + namespace mem + { + void initialize_memory(void); + void terminate_memory (void); + + template + T* allocT(u64 num_elements, MEMORY_TAG tag); + void* alloc(u64 num_bytes, MEMORY_TAG tag); + void dealloc(void* ptr, u64 num_bytes, MEMORY_TAG tag); + template + T* setzeroT(T* dst, u64 num_elements); + void* setzero(void* dst, u64 num_bytes); + + void changetag(u64 nbytes, MEMORY_TAG prev, MEMORY_TAG next); + + i64 get_total_memory_usage(void); + i64 get_memory_usage_for_class(MEMORY_TAG memory_class, i64 out_per_zone[MEMORY_TAG_CLASS_MAX]); + i64 get_memory_usage_for_zone(MEMORY_TAG memory_zone, i64 out_per_class[MEMORY_TAG_ZONE_MAX]); + + // Must be freed by user via dealloc with the tags; + // MEMORY_TAG_CLASS_STRING | MEMORY_TAG_ZONE_DEBUG | MEMORY_TAG_ALIGN_ANY + char* get_memory_usage_string(void); + + i64 get_memory_tag_class_name(MEMORY_TAG memory_class, char* out_buf, u64 out_buf_size); + i64 get_memory_tag_zone_name(MEMORY_TAG zone_class, char* out_buf, u64 out_buf_size); + + template + T* allocT(u64 num_elements, MEMORY_TAG tag) + { return alloc(num_elements*sizeof(T), tag); } + template + T* setzeroT(T * dst, u64 num_elements) + { return setzero(dst, num_elements*sizeof(T)); } + } +} diff --git a/src/core/basalt_logger.cpp b/src/core/basalt_logger.cpp new file mode 100644 index 0000000..ccb5108 --- /dev/null +++ b/src/core/basalt_logger.cpp @@ -0,0 +1,185 @@ +#include +#include +#include +#include "core/basalt_logger.h" +#include "memory.h" + +static struct { + logger_stream_t streams[BASALT_LOGGER_MAX_STREAMS]; + u8 global_mask; + u8 num_streams; + u8 initialized; + u8 _padding[5];// Unused - Signifies how many bytes aren't being used +} logger_state; + +void initialize_logger() +{ + if (logger_state.initialized != 0x00) + return; + memset(&logger_state, 0, sizeof(logger_state)); +#if defined(DEBUG) || defined(_DEBUG) + logger_state.global_mask = LOG_MASK_ALL; +#else + logger_state.global_mask = LOG_MASK_DEFAULT; +#endif + logger_state.num_streams = 2; + logger_add_stream(stdout, LOG_MASK_INFO | LOG_MASK_DEBUGGING, LOG_STREAM_FLAG_SUPPORTS_ANSI_BIT | LOG_STREAM_FLAG_INCLUDE_DATETIME_BIT); + logger_add_stream(stderr, LOG_MASK_ERRORS, LOG_STREAM_FLAG_SUPPORTS_ANSI_BIT | LOG_STREAM_FLAG_INCLUDE_DATETIME_BIT); + logger_state.initialized = 0xff; +} + +void terminate_logger() +{ + if (logger_state.initialized != 0xff) + return; + for (u8 i = 0; i < BASALT_LOGGER_MAX_STREAMS; ++i) + logger_remove_stream(i); + memset(&logger_state, 0, sizeof(logger_state)); + logger_state.initialized = 0x00; +} + +inline FILE* logger_stream_get_file(logger_stream_t* state) +{ + return (FILE*)((state->stream & 0xffffffffffff) | ((u64)stdout & 0xffff0000000000)); +} + +u8 logger_add_stream(FILE* output, LOG_LEVEL mask, LOG_STREAM_FLAGS flags) +{ + if (logger_state.num_streams == BASALT_LOGGER_MAX_STREAMS) + return -1; + if ((((u64)output) & 0xffffffffffff) != 0) + __builtin_trap(); + for (u8 i = 0; i < BASALT_LOGGER_MAX_STREAMS; ++i) + { + if ((logger_state.streams[i].flags & LOG_STREAM_FLAG_INTERNAL_ALLOCATED_BIT) == 0) + { + logger_state.streams[i].stream = (u64)output; + logger_state.streams[i].flags = flags; + logger_state.streams[i].mask = mask; + logger_state.num_streams++; + return i; + } + } + return -1; +} + +void logger_remove_stream(u8 index) +{ + if (index >= sizeof(logger_state.streams) / sizeof(logger_state.streams[0])) + return; + logger_stream_t* stream = logger_state.streams+index; + FILE* f = logger_stream_get_file(stream); + if (f != stdout && f != stderr) + fclose(f); + stream->flags = 0; + stream->mask = 0; + stream->stream = (u64)nullptr; + logger_state.num_streams--; +} + +u8 logger_find_stream(FILE* target) +{ + for (size_t i = 0; i < BASALT_LOGGER_MAX_STREAMS; ++i) + { + if (logger_stream_get_file(logger_state.streams + i) == target && + (logger_state.streams[i].flags & LOG_STREAM_FLAG_INTERNAL_ALLOCATED_BIT) != 0) + return i; + } + return (u8)(-1); +} + +void basalt_log(const LOG_LEVEL level, const char* msg, ...) +{ + const char log_prefixes[8][6] = { "FATAL", "ERROR", "WARN ", "INFO ", "OK ", "WEIRD", "DEBUG", "TRACE" }; + const char* log_ansi_prefixes[8] = { "\033[38;5;0m\033[48;5;9m", "\033[38;5;9m", "\033[38;5;11m", "\033[38;5;15m", "\033[38;5;10m", "\033[38;5;13m", "\033[38;5;12m", "\033[38;5;6m" }; + if (level == 0) + return; + const u8 level = (u8)__builtin_ctz(level); + if (level > 8) + { + debug_break(); + return; + } + + if ((level & logger_state.global_mask) == 0) + return; + + u8 valid_streams = 0; + for (u8 i = 0; i < BASALT_LOGGER_MAX_STREAMS; ++i) + { + logger_stream_t* stream = logger_state.streams + i; + if ((stream->flags & LOG_STREAM_FLAG_INTERNAL_ALLOCATED_BIT) == 0) + continue; + valid_streams++; + if ((stream->mask & level) == 0) + continue; + FILE* out = logger_stream_get_file(stream); + if (stream->flags & LOG_STREAM_FLAG_SUPPORTS_ANSI_BIT) + fprintf(out, log_ansi_prefixes[level]); + if ((stream->flags & LOG_STREAM_FLAG_DISABLE_PREFIXES) == 0) + { + if (stream->flags & LOG_STREAM_FLAG_INCLUDE_DATETIME_BIT) + { + char time_buffer[32]; + const time_t timer = time(NULL); + struct tm tm_info; + errno_t err = localtime_s(&tm_info, &timer); + if (!err) + { + int n_chars = (int)strftime(time_buffer, sizeof(time_buffer), "%d-%m-%y %R:%S", &tm_info); + fprintf(out, "[%*s %*s]: ", sizeof(log_prefixes[0]), log_prefixes[level], n_chars, time_buffer); + } + else + fprintf(out, "[%*s]: ", sizeof(log_prefixes[0]), log_prefixes[level]); + } + else + fprintf(out, "[%*s]: ", sizeof(log_prefixes[0]), log_prefixes[level]); + } + __builtin_va_list argstart; + va_start(argstart, msg); + vfprintf(out, msg, argstart); + va_end(argstart); + + if ((stream->flags & LOG_STREAM_FLAG_SUPPORTS_ANSI_BIT) != 0) + fprintf(out, "\033[m"); + } +} + +void basalt_write(const LOG_LEVEL level, const char* msg, ...) +{ + const char log_prefixes[8][6] = { "FATAL", "ERROR", "WARN ", "INFO ", "OK ", "WEIRD", "DEBUG", "TRACE" }; + const char* log_ansi_prefixes[8] = { "\033[38;5;0m\033[48;5;9m", "\033[38;5;9m", "\033[38;5;11m", "\033[38;5;15m", "\033[38;5;10m", "\033[38;5;13m", "\033[38;5;12m", "\033[38;5;6m" }; + if (level == 0) + return; + const u8 level = (u8)__builtin_ctz(level); + if (level > 8) + { + debug_break(); + return; + } + + if ((level & logger_state.global_mask) == 0) + return; + + u8 valid_streams = 0; + for (u8 i = 0; i < BASALT_LOGGER_MAX_STREAMS; ++i) + { + logger_stream_t* stream = logger_state.streams + i; + if ((stream->flags & LOG_STREAM_FLAG_INTERNAL_ALLOCATED_BIT) == 0) + continue; + valid_streams++; + if ((stream->mask & level) == 0) + continue; + FILE* out = logger_stream_get_file(stream); + if (stream->flags & LOG_STREAM_FLAG_SUPPORTS_ANSI_BIT) + fprintf(out, log_ansi_prefixes[level]); + + __builtin_va_list argstart; + va_start(argstart, msg); + vfprintf(out, msg, argstart); + va_end(argstart); + + if ((stream->flags & LOG_STREAM_FLAG_SUPPORTS_ANSI_BIT) != 0) + fprintf(out, "\033[m"); + } +} diff --git a/src/core/basalt_memory.cpp b/src/core/basalt_memory.cpp new file mode 100644 index 0000000..50c0051 --- /dev/null +++ b/src/core/basalt_memory.cpp @@ -0,0 +1,235 @@ +#include "core/basalt_logger.h" +#include "core/basalt_memory.h" + +#include +#include +#include +#include + + +static struct { + i64 alloc_total; + i64 class_alloc[MEMORY_TAG_CLASS_MAX+1]; + i64 zone_alloc[MEMORY_TAG_ZONE_MAX + 1]; +} basalt_memory_state; + +static const char* basalt_memory_class_names[MEMORY_TAG_CLASS_MAX_BUILTIN]; +static const char* basalt_memory_zone_names[MEMORY_TAG_ZONE_MAX_BUILTIN]; +static u8 basalt_memory_class_name_lengths[MEMORY_TAG_CLASS_MAX_BUILTIN]; +static u8 basalt_memory_zone_name_lengths[MEMORY_TAG_ZONE_MAX_BUILTIN]; + +void basalt::mem::initialize_memory(void) +{ + memset(&basalt_memory_state, 0, sizeof(basalt_memory_state)); + for (size_t i = 0; i < MEMORY_TAG_CLASS_MAX_BUILTIN; ++i) + basalt_memory_class_name_lengths[i] = strnlen(basalt_memory_class_names[i], 64); + for (size_t i = 0; i < MEMORY_TAG_ZONE_MAX_BUILTIN; ++i) + basalt_memory_zone_name_lengths[i] = strnlen(basalt_memory_zone_names[i], 64); +} + +void basalt::mem::terminate_memory(void) {} + +void* basalt::mem::alloc(u64 num_bytes, MEMORY_TAG tag) +{ + void* ptr = nullptr; + if ((tag & MEMORY_TAG_MASK_ALIGN) == MEMORY_TAG_ALIGN_ANY) + ptr = malloc(num_bytes); + else if ((tag & MEMORY_TAG_MASK_ALIGN) == MEMORY_TAG_ALIGN_32) + ptr = _mm_malloc(num_bytes, 32); + else if ((tag & MEMORY_TAG_MASK_ALIGN) == MEMORY_TAG_ALIGN_64) + ptr = _mm_malloc(num_bytes, 64); + else if ((tag & MEMORY_TAG_MASK_ALIGN) == MEMORY_TAG_ALIGN_PAGE) + ptr = _mm_malloc(num_bytes, 4096); + if (ptr != nullptr) + { + basalt_memory_state.alloc_total += num_bytes; + basalt_memory_state.class_alloc[(MEMORY_TAG_MASK_CLASS & tag) >> MEMORY_TAG_SHIFT_CLASS] += num_bytes; + basalt_memory_state.zone_alloc[(MEMORY_TAG_MASK_ZONE & tag) >> MEMORY_TAG_SHIFT_ZONE] += num_bytes; + } + return ptr; +} + +void basalt::mem::dealloc(void* ptr, u64 num_bytes, MEMORY_TAG tag) +{ + if (ptr == nullptr) + return; + _mm_free(ptr); + basalt_memory_state.alloc_total += num_bytes; + basalt_memory_state.class_alloc[(MEMORY_TAG_MASK_CLASS & tag) >> MEMORY_TAG_SHIFT_CLASS] += num_bytes; + basalt_memory_state.zone_alloc[(MEMORY_TAG_MASK_ZONE & tag) >> MEMORY_TAG_SHIFT_ZONE] += num_bytes; +} + +void* basalt::mem::setzero(void* dst, u64 num_bytes) +{ return memset(dst, 0, num_bytes); } + +void basalt::mem::changetag(u64 nbytes, MEMORY_TAG prev, MEMORY_TAG next) +{ + BASSERT_FATAL((prev & MEMORY_TAG_MASK_ALIGN) == (next & MEMORY_TAG_MASK_ALIGN), "Assertion %s failed at %s:%d\n\tCan not change memory alignment tag after allocation!\n"); + if ((prev & MEMORY_TAG_MASK_CLASS) != (next & MEMORY_TAG_MASK_CLASS)) + { + basalt_memory_state.class_alloc[(MEMORY_TAG_MASK_CLASS & prev) >> MEMORY_TAG_SHIFT_CLASS] -= nbytes; + basalt_memory_state.class_alloc[(MEMORY_TAG_MASK_CLASS & next) >> MEMORY_TAG_SHIFT_CLASS] += nbytes; + } + if ((prev & MEMORY_TAG_MASK_ZONE) != (next & MEMORY_TAG_MASK_ZONE)) + { + basalt_memory_state.zone_alloc[(MEMORY_TAG_MASK_ZONE & prev) >> MEMORY_TAG_SHIFT_ZONE] -= nbytes; + basalt_memory_state.zone_alloc[(MEMORY_TAG_MASK_ZONE & next) >> MEMORY_TAG_SHIFT_ZONE] += nbytes; + } +} + +i64 basalt::mem::get_total_memory_usage(void) +{ return basalt_memory_state.alloc_total; } + +i64 basalt::mem::get_memory_usage_for_class(MEMORY_TAG memory_class, i64 out_per_class[MEMORY_TAG_CLASS_MAX]) +{ + if (out_per_class != nullptr) + memcpy(out_per_class, basalt_memory_state.class_alloc, sizeof(i64)*MEMORY_TAG_CLASS_MAX); + return basalt_memory_state.class_alloc[(memory_class & MEMORY_TAG_MASK_CLASS) >> MEMORY_TAG_SHIFT_CLASS]; +} + +i64 basalt::mem::get_memory_usage_for_zone(MEMORY_TAG memory_zone, i64 out_per_zone[MEMORY_TAG_ZONE_MAX]) +{ + if (out_per_zone != nullptr) + memcpy(out_per_zone, basalt_memory_state.zone_alloc, sizeof(i64) * MEMORY_TAG_ZONE_MAX); + return basalt_memory_state.zone_alloc[(memory_zone & MEMORY_TAG_MASK_ZONE) >> MEMORY_TAG_SHIFT_ZONE]; +} + +f64 get_eng_unit(f64 x, char* unit) +{ + constexpr i8 order_index_offset = 5; + const char units[] = {'f', 'p', 'n', 'u', 'm', ' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Y'}; + i8 order = 0; + while (x > 1000.0 && (order < (sizeof(units) - sizeof(char) * (order_index_offset-1)))) + { + order++; + x /= 1000.0; + } + while (x < 1.0 && (order+order_index_offset) > 0) + { + order--; + x *= 1000.0; + } + *unit = units[order + order_index_offset]; +} + +char* basalt::mem::get_memory_usage_string(void) +{ + constexpr size_t buffer_size = 2048; + + static i32 class_pad_amt = 0; + if (class_pad_amt) + { + for (u32 i = 0; i < MEMORY_TAG_CLASS_MAX_BUILTIN; ++i) + class_pad_amt = class_pad_amt < basalt_memory_class_name_lengths[i] ? basalt_memory_class_name_lengths[i] : class_pad_amt; + const u32 num_class_user_digits = (u32)ceilf(log10f(MEMORY_TAG_CLASS_MAX - MEMORY_TAG_CLASS_MAX_BUILTIN)); + class_pad_amt = class_pad_amt < num_class_user_digits ? num_class_user_digits : class_pad_amt; + } + static i32 zone_pad_amt = 0; + if (zone_pad_amt) + { + for (u32 i = 0; i < MEMORY_TAG_ZONE_MAX_BUILTIN; ++i) + zone_pad_amt = zone_pad_amt < basalt_memory_zone_name_lengths[i] ? basalt_memory_zone_name_lengths[i] : zone_pad_amt; + const u32 num_zone_user_digits = (u32)ceilf(log10f(MEMORY_TAG_ZONE_MAX - MEMORY_TAG_ZONE_MAX_BUILTIN)); + zone_pad_amt = zone_pad_amt < num_zone_user_digits ? num_zone_user_digits : zone_pad_amt; + } + + size_t offset = 0; + char* ret = (char*)basalt::mem::alloc(buffer_size*sizeof(char), + MEMORY_TAG_CLASS_STRING | MEMORY_TAG_ZONE_DEBUG | MEMORY_TAG_ALIGN_ANY); + char name_buf[256]; + + char unit = ' '; + f64 val = get_eng_unit(basalt_memory_state.alloc_total, &unit); + offset += snprintf(ret+offset, buffer_size - offset - 1, "Total memory usage: %6.2f %cB\nMemory usage by class:\n", val, unit); + for (u32 i = 0; i <= MEMORY_TAG_CLASS_MAX; ++i) + { + val = get_eng_unit(basalt_memory_state.class_alloc[i], &unit); + name_buf[get_memory_tag_class_name(i << MEMORY_TAG_SHIFT_CLASS, name_buf, sizeof(name_buf)-1)] = '\0'; + offset += snprintf(ret+offset, buffer_size - offset - 1, "%-*s: %6.2f %cB\n", class_pad_amt, name_buf, val, unit); + } + offset += snprintf(ret+offset, buffer_size - offset - 1, "Memory usage by location:\n"); + for (u32 i = 0; i <= MEMORY_TAG_CLASS_MAX; ++i) + { + val = get_eng_unit(basalt_memory_state.zone_alloc[i], &unit); + name_buf[get_memory_tag_zone_name(i << MEMORY_TAG_SHIFT_ZONE, name_buf, sizeof(name_buf) - 1)] = '\0'; + offset += snprintf(ret + offset, buffer_size - offset - 1, "%-*s: %6.2f %cB\n", zone_pad_amt, name_buf, val, unit); + } + ret[offset] = '\0'; + + return ret; +} + +static const char* basalt_memory_class_names[MEMORY_TAG_CLASS_MAX_BUILTIN] = { + "UNKNOWN", + "ARRAY", + "DYNARRAY", + "STRING", + "CIRCULAR_BUFFER", + "DICT", + "BINTREE", + "OCTTREE", + "TEXTURE", + "JOB", + "TRANSFORM", + "RENDER_OBJECT", + "MATERIAL" +}; +static const char* basalt_memory_zone_names[MEMORY_TAG_ZONE_MAX_BUILTIN] = { + "UNKNOWN", + "SCENE", + "APPLICATION", + "RENDERER", + "ENGINE", + "EVENT", + "DEBUG", + "AUDIO" +}; + +i64 basalt::mem::get_memory_tag_class_name(MEMORY_TAG memory_class, char* out_buf, u64 out_buf_size) +{ + memory_class = (memory_class & MEMORY_TAG_MASK_CLASS) >> MEMORY_TAG_SHIFT_CLASS; + if (memory_class < MEMORY_TAG_CLASS_MAX_BUILTIN) + { + if (out_buf_size < basalt_memory_class_name_lengths[memory_class]) + return 0; + memcpy(out_buf, basalt_memory_class_names[memory_class], basalt_memory_class_name_lengths[memory_class]); + } + + // Calculate the user postfix value + constexpr u16 user_index_max = MEMORY_TAG_CLASS_MAX - MEMORY_TAG_CLASS_MAX_BUILTIN + 1; + u16 user_index = ((memory_class & MEMORY_TAG_ZONE_MAX) >> MEMORY_TAG_SHIFT_ZONE); + if (user_index > user_index_max) + return 0; + user_index = user_index_max - user_index; + + // Calculate the number of digits the number will contain + u16 ndigits = (u16)ceil(log10l(user_index)); + if ((ndigits + 7) > out_buf_size) + return 0; + // Convert the string to an int and prefix it with USER_ + snprintf(out_buf, out_buf_size, "USER_%u", user_index); + return 7+ndigits*sizeof(char); +} + +i64 basalt::mem::get_memory_tag_zone_name(MEMORY_TAG zone_class, char* out_buf, u64 out_buf_size) +{ + zone_class = (zone_class & MEMORY_TAG_MASK_ZONE) >> MEMORY_TAG_SHIFT_ZONE; + if (zone_class < MEMORY_TAG_ZONE_MAX_BUILTIN) + { + if (out_buf_size < basalt_memory_zone_name_lengths[zone_class]) + return 0; + memcpy(out_buf, basalt_memory_zone_names[zone_class], basalt_memory_zone_name_lengths[zone_class]); + } + constexpr u16 user_index_max = MEMORY_TAG_CLASS_MAX - MEMORY_TAG_CLASS_MAX_BUILTIN + 1; + + u16 user_index = ((zone_class & MEMORY_TAG_ZONE_MAX) >> MEMORY_TAG_SHIFT_ZONE); + if (user_index > user_index_max) + return 0; + user_index = user_index_max - user_index; + + u16 ndigits = (u16)ceil(log10l(user_index)); + if ((ndigits + 7) > out_buf_size) + return 0; + snprintf(out_buf, out_buf_size, "USER_%u", user_index); + return 7 + ndigits * sizeof(char); +}