diff --git a/architecture/arm/Intrinsics.h b/architecture/arm/Intrinsics.h index 18cfbde..b64025a 100644 --- a/architecture/arm/Intrinsics.h +++ b/architecture/arm/Intrinsics.h @@ -40,6 +40,9 @@ #define intrin_bits_count_64(data) compiler_popcount_64((data)) #define intrin_prefetch(mem) compiler_prefetch((mem)) +#define intrin_prefetch_l1(mem) compiler_prefetch_l1((mem)) +#define intrin_prefetch_l2(mem) compiler_prefetch_l2((mem)) +#define intrin_prefetch_l3(mem) compiler_prefetch_l3((mem)) #if _WIN32 #define intrin_timestamp_counter() ({ uint64_t cntvct; asm volatile("mrs %0, cntvct_el0" : "=r"(cntvct)); cntvct; }) diff --git a/architecture/x86/Intrinsics.h b/architecture/x86/Intrinsics.h index 6a8172a..5d8bc01 100644 --- a/architecture/x86/Intrinsics.h +++ b/architecture/x86/Intrinsics.h @@ -54,7 +54,9 @@ #define intrin_bits_count_32(data) _mm_popcnt_u32((data)) #define intrin_bits_count_64(data) _mm_popcnt_u64((data)) -#define intrin_prefetch(mem) _mm_prefetch((mem)) +#define intrin_prefetch_l1(mem) _mm_prefetch((mem), _MM_HINT_T0) +#define intrin_prefetch_l2(mem) _mm_prefetch((mem), _MM_HINT_T1) +#define intrin_prefetch_l3(mem) _mm_prefetch((mem), _MM_HINT_T2) inline uint64 intrin_timestamp_counter() { diff --git a/compiler/gcc/CompilerUtils.h b/compiler/gcc/CompilerUtils.h index 69766d5..1ada586 100644 --- a/compiler/gcc/CompilerUtils.h +++ b/compiler/gcc/CompilerUtils.h @@ -29,6 +29,9 @@ #define __restrict __restrict__ #define compiler_prefetch(mem) __builtin_prefetch((mem), 0, 3) +#define compiler_prefetch_l1(mem) __builtin_prefetch((mem), 0, 3) +#define compiler_prefetch_l2(mem) __builtin_prefetch((mem), 0, 2) +#define compiler_prefetch_l3(mem) __builtin_prefetch((mem), 0, 1) int32 compiler_find_first_bit_r2l(uint64 mask) { if (!mask) { diff --git a/compiler/msvc/CompilerUtils.h b/compiler/msvc/CompilerUtils.h index d697833..4ddd1e4 100644 --- a/compiler/msvc/CompilerUtils.h +++ b/compiler/msvc/CompilerUtils.h @@ -31,6 +31,9 @@ typedef SSIZE_T ssize_t; #define compiler_popcount_64(data) __popcnt64((data)) #define compiler_prefetch(mem) __prefetch((mem)) +#define compiler_prefetch_l1(mem) __prefetch((mem)) +#define compiler_prefetch_l2(mem) __prefetch((mem)) +#define compiler_prefetch_l3(mem) __prefetch((mem)) inline int32 compiler_find_first_bit_r2l(uint64 mask) { @@ -39,9 +42,7 @@ int32 compiler_find_first_bit_r2l(uint64 mask) { } unsigned long index; - _BitScanForward64(&index, mask); - - return index; + return _BitScanForward64(&index, mask) ? index : -1; } inline @@ -51,9 +52,7 @@ int32 compiler_find_first_bit_r2l(uint32 mask) { } unsigned long index; - _BitScanForward(&index, mask); - - return index; + return _BitScanForward(&index, mask) ? index : -1; } inline @@ -63,9 +62,7 @@ int32 compiler_find_first_bit_l2r(uint64 mask) { } unsigned long index; - _BitScanReverse64(&index, mask); - - return index; + return _BitScanReverse64(&index, mask) ? index : -1; } inline @@ -75,9 +72,7 @@ int32 compiler_find_first_bit_l2r(uint32 mask) { } unsigned long index; - _BitScanReverse(&index, mask); - - return index; + return _BitScanReverse(&index, mask) ? index : -1; } #endif \ No newline at end of file diff --git a/hash/GeneralHash.h b/hash/GeneralHash.h index 5bd39e7..1856bb9 100644 --- a/hash/GeneralHash.h +++ b/hash/GeneralHash.h @@ -114,6 +114,175 @@ uint32 hash_ejb(const char* str) return hash % PRIME2; } +#define ROTL32(x, r) ((x) << (r)) | ((x) >> (32 - (r))) +inline constexpr +uint32 hash_murmur3_32(const byte* key, size_t len, uint32 seed = 0) { + uint32 h = seed; + uint32 k; + + // Process 32-bit blocks (4 bytes at a time) + for (size_t i = 0; i < len / 4; ++i) { + // Get 4 bytes from the key + k = *(uint32 *) (key + i * 4); + + // Mix the 4 bytes into the hash + k *= 0xcc9e2d51; + k = ROTL32(k, 15); + k *= 0x1b873593; + + h ^= k; + h = ROTL32(h, 13); + h = h * 5 + 0xe6546b64; + } + + // Handle the remaining bytes (less than 4 bytes) + k = 0; + switch (len & 3) { + case 3: + k ^= key[len / 4 * 4 + 2] << 16; + [[fallthrough]]; + case 2: + k ^= key[len / 4 * 4 + 1] << 8; + [[fallthrough]]; + case 1: + k ^= key[len / 4 * 4]; + k *= 0xcc9e2d51; + k = ROTL32(k, 15); + k *= 0x1b873593; + h ^= k; + } + + // Finalize the hash + h ^= len; + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + + return h; +} + +#define ROTL64(x, r) ((x) << (r)) | ((x) >> (64 - (r))) +uint64 hash_murmur3_64(const void* key, size_t len, uint64 seed = 0) { + const uint64 c1 = 0x87c37b91114253d5ULL; + const uint64 c2 = 0x4cf5ad432745937fULL; + + const byte* data = (const byte*) key; + const size_t nblocks = len / 16; + + uint64 h1 = seed; + uint64 h2 = seed; + + // Process 128-bit blocks (16 bytes at a time) + for (size_t i = 0; i < nblocks; ++i) { + uint64 k1 = *(uint64 *) (data + i * 16); + uint64 k2 = *(uint64 *) (data + i * 16 + 8); + + k1 *= c1; + k1 = ROTL64(k1, 31); + k1 *= c2; + h1 ^= k1; + + h1 = ROTL64(h1, 27); + h1 += h2; + h1 = h1 * 5 + 0x52dce729; + + k2 *= c2; + k2 = ROTL64(k2, 33); + k2 *= c1; + h2 ^= k2; + + h2 = ROTL64(h2, 31); + h2 += h1; + h2 = h2 * 5 + 0x38495ab5; + } + + // Handle the remaining bytes (less than 16 bytes) + const byte* tail = data + nblocks * 16; + uint64 k1 = 0; + uint64 k2 = 0; + + switch (len & 15) { + case 15: + k2 ^= ((uint64) tail[14]) << 48; + [[fallthrough]]; + case 14: + k2 ^= ((uint64) tail[13]) << 40; + [[fallthrough]]; + case 13: + k2 ^= ((uint64) tail[12]) << 32; + [[fallthrough]]; + case 12: + k2 ^= ((uint64) tail[11]) << 24; + [[fallthrough]]; + case 11: + k2 ^= ((uint64) tail[10]) << 16; + [[fallthrough]]; + case 10: + k2 ^= ((uint64) tail[9]) << 8; + [[fallthrough]]; + case 9: + k2 ^= ((uint64) tail[8]) << 0; + k2 *= c2; + k2 = ROTL64(k2, 33); + k2 *= c1; + h2 ^= k2; + [[fallthrough]]; + case 8: + k1 ^= ((uint64) tail[7]) << 56; + [[fallthrough]]; + case 7: + k1 ^= ((uint64) tail[6]) << 48; + [[fallthrough]]; + case 6: + k1 ^= ((uint64) tail[5]) << 40; + [[fallthrough]]; + case 5: + k1 ^= ((uint64) tail[4]) << 32; + [[fallthrough]]; + case 4: + k1 ^= ((uint64) tail[3]) << 24; + [[fallthrough]]; + case 3: + k1 ^= ((uint64) tail[2]) << 16; + [[fallthrough]]; + case 2: + k1 ^= ((uint64) tail[1]) << 8; + [[fallthrough]]; + case 1: + k1 ^= ((uint64) tail[0]) << 0; + k1 *= c1; + k1 = ROTL64(k1, 31); + k1 *= c2; + h1 ^= k1; + } + + // Finalize the hash + h1 ^= len; + h2 ^= len; + + h1 += h2; + h2 += h1; + + h1 ^= h1 >> 33; + h1 *= 0xff51afd7ed558ccdULL; + h1 ^= h1 >> 33; + h1 *= 0xc4ceb9fe1a85ec53ULL; + h1 ^= h1 >> 33; + + h2 ^= h2 >> 33; + h2 *= 0xff51afd7ed558ccdULL; + h2 ^= h2 >> 33; + h2 *= 0xc4ceb9fe1a85ec53ULL; + h2 ^= h2 >> 33; + + h1 += h2; + // h2 += h1; + + return h1; +} + //////////////////////////////////// // Seeded hash functions //////////////////////////////////// diff --git a/image/Tga.h b/image/Tga.h index ed006a9..f57e355 100644 --- a/image/Tga.h +++ b/image/Tga.h @@ -79,7 +79,7 @@ void generate_default_tga_references(const FileBody* file, Tga* tga) void image_tga_generate(const FileBody* src_data, Image* image) { // @performance We are generating the struct and then filling the data. - // There is some asignment/copy overhead + // There is some assignment/copy overhead Tga src = {}; generate_default_tga_references(src_data, &src); diff --git a/log/Debug.cpp b/log/Debug.cpp deleted file mode 100644 index 908055c..0000000 --- a/log/Debug.cpp +++ /dev/null @@ -1,447 +0,0 @@ -#ifndef TOS_LOG_DEBUG_MEMORY_C -#define TOS_LOG_DEBUG_MEMORY_C - -#include "../stdlib/Types.h" -#include "Debug.h" -#include "DebugMemory.h" -#include "Log.h" -#include "TimingStat.h" -#include "../utils/StringUtils.h" -#include "../utils/TestUtils.h" -#include "../thread/Atomic.h" -#include "../architecture/Intrinsics.h" -#include "../compiler/CompilerUtils.h" - -global_persist DebugContainer* debug_container = NULL; - -// WARNING: Spinlock uses TimeUtils which uses performance counter, which is part of DebugContainer -// @todo The explanation above is insane. We did this so we only have to set the performance counter once but it is biting us now -#include "../thread/Spinlock.cpp" - -#if _WIN32 - #include - void setup_performance_count() { - if (!debug_container) { - return; - } - - LARGE_INTEGER perf_counter; - QueryPerformanceFrequency(&perf_counter); - debug_container->performance_count_frequency = perf_counter.QuadPart; - } -#elif __linux__ - void setup_performance_count() { - if (!debug_container) { - return; - } - - FILE* fp = fopen("/proc/cpuinfo", "r"); - if (!fp) { - return; - } - - char line[256]; - uint64 cpu_freq = 0; - while (fgets(line, sizeof(line), fp)) { - if (sscanf(line, "cpu MHz%*[^0-9]%ld", &cpu_freq) == 1) { - break; - } - } - - fclose(fp); - - debug_container->performance_count_frequency = cpu_freq == 0 ? 1 : cpu_freq * 1000000; - } -#endif - -void log_to_file() -{ - // we don't log an empty log pool - if (!debug_container || debug_container->log_memory.pos == 0 || !debug_container->log_fp) { - return; - } - - #if _WIN32 - DWORD written; - WriteFile( - debug_container->log_fp, - (char *) debug_container->log_memory.memory, - (uint32) debug_container->log_memory.pos, - &written, - NULL - ); - #else - if (debug_container->log_fp < 0) { - return; - } - - write( - debug_container->log_fp, - (char *) debug_container->log_memory.memory, - (uint32) debug_container->log_memory.pos - ); - #endif -} - -// IMPORTANT: This function should only be called when you actually use this data -// e.g. log to display or file -inline -void update_timing_stat(uint32 stat, const char* function) -{ - uint64 new_tick_count = intrin_timestamp_counter(); - - TimingStat* timing_stat = &debug_container->perf_stats[stat]; - - spinlock_start(&debug_container->perf_stats_spinlock); - timing_stat->function = function; - timing_stat->delta_tick = (uint32) (new_tick_count - timing_stat->old_tick_count); - timing_stat->delta_time = (f64) timing_stat->delta_tick / (f64) debug_container->performance_count_frequency; - timing_stat->old_tick_count = new_tick_count; - spinlock_end(&debug_container->perf_stats_spinlock); -} - -inline -void update_timing_stat_start(uint32 stat, const char*) -{ - spinlock_start(&debug_container->perf_stats_spinlock); - debug_container->perf_stats[stat].old_tick_count = intrin_timestamp_counter(); - spinlock_end(&debug_container->perf_stats_spinlock); -} - -inline -void update_timing_stat_end(uint32 stat, const char* function) -{ - uint64 new_tick_count = intrin_timestamp_counter(); - - TimingStat* timing_stat = &debug_container->perf_stats[stat]; - - spinlock_start(&debug_container->perf_stats_spinlock); - timing_stat->function = function; - timing_stat->delta_tick = (uint32) (new_tick_count - timing_stat->old_tick_count); - timing_stat->delta_time = (f64) timing_stat->delta_tick / (f64) debug_container->performance_count_frequency; - timing_stat->old_tick_count = new_tick_count; - spinlock_end(&debug_container->perf_stats_spinlock); -} - -inline -void update_timing_stat_end_continued(uint32 stat, const char* function) -{ - uint64 new_tick_count = intrin_timestamp_counter(); - - TimingStat* timing_stat = &debug_container->perf_stats[stat]; - - spinlock_start(&debug_container->perf_stats_spinlock); - timing_stat->function = function; - timing_stat->delta_tick = (uint32) ((uint32) (new_tick_count - timing_stat->old_tick_count) + timing_stat->delta_tick); - timing_stat->delta_time = timing_stat->delta_time + (f64) timing_stat->delta_tick / (f64) debug_container->performance_count_frequency; - timing_stat->old_tick_count = new_tick_count; - spinlock_end(&debug_container->perf_stats_spinlock); -} - -inline -void update_timing_stat_reset(uint32 stat) -{ - TimingStat* timing_stat = &debug_container->perf_stats[stat]; - - spinlock_start(&debug_container->perf_stats_spinlock); - timing_stat->function = NULL; - timing_stat->delta_tick = 0; - timing_stat->delta_time = 0; - spinlock_end(&debug_container->perf_stats_spinlock); -} - -inline -void reset_counter(int32 id) -{ - if (!debug_container || !debug_container->counter) { - return; - } - - atomic_set_acquire(&debug_container->counter[id], 0); -} - -inline -void log_increment(int32 id, int64 by = 1) -{ - if (!debug_container || !debug_container->counter) { - return; - } - - atomic_add_acquire(&debug_container->counter[id], by); -} - -inline -void log_counter(int32 id, int64 value) -{ - if (!debug_container || !debug_container->counter) { - return; - } - - atomic_set_acquire(&debug_container->counter[id], value); -} - -inline -DebugMemory* debug_memory_find(uintptr_t start) -{ - for (uint64 i = 0; i < debug_container->dmc.memory_size; ++i) { - if (debug_container->dmc.memory_stats[i].start <= start - && debug_container->dmc.memory_stats[i].start + debug_container->dmc.memory_stats[i].size >= start - ) { - return &debug_container->dmc.memory_stats[i]; - } - } - - return NULL; -} - -void debug_memory_init(uintptr_t start, uint64 size) -{ - if (!start || !debug_container) { - return; - } - - const DebugMemory* mem = debug_memory_find(start); - if (mem) { - return; - } - - DebugMemoryContainer* dmc = &debug_container->dmc; - if (dmc->memory_size <= dmc->memory_element_idx) { - DebugMemory* old = dmc->memory_stats; - - dmc->memory_size += 3; - dmc->memory_stats = (DebugMemory *) calloc(dmc->memory_size, sizeof(DebugMemory)); - - if (old) { - memcpy(dmc->memory_stats, old, (dmc->memory_size - 3) * sizeof(DebugMemory)); - free(old); - } - } - - DebugMemory* debug_mem = &dmc->memory_stats[dmc->memory_element_idx]; - debug_mem->start = start; - debug_mem->size = size; - debug_mem->usage = 0; - - ++dmc->memory_element_idx; -} - -void debug_memory_log(uintptr_t start, uint64 size, int32 type, const char* function) -{ - if (!start || !debug_container) { - return; - } - - DebugMemory* mem = debug_memory_find(start); - if (!mem) { - return; - } - - uint64 idx = atomic_fetch_add_relaxed(&mem->action_idx, 1); - if (idx >= ARRAY_COUNT(mem->last_action)) { - atomic_set_acquire(&mem->action_idx, 1); - idx %= ARRAY_COUNT(mem->last_action); - } - - DebugMemoryRange* dmr = &mem->last_action[idx]; - dmr->type = type; - dmr->start = start - mem->start; - dmr->size = size; - - dmr->time = intrin_timestamp_counter(); - dmr->function_name = function; - - if (type < 0 && mem->usage < size * -type) { - mem->usage = 0; - } else { - mem->usage += size * type; - } -} - -void debug_memory_reserve(uintptr_t start, uint64 size, int32 type, const char* function) -{ - if (!start || !debug_container) { - return; - } - - DebugMemory* mem = debug_memory_find(start); - if (!mem) { - return; - } - - uint64 idx = atomic_fetch_add_relaxed(&mem->reserve_action_idx, 1); - if (idx >= ARRAY_COUNT(mem->reserve_action)) { - atomic_set_acquire(&mem->reserve_action_idx, 1); - idx %= ARRAY_COUNT(mem->reserve_action); - } - - DebugMemoryRange* dmr = &mem->reserve_action[idx]; - dmr->type = type; - dmr->start = start - mem->start; - dmr->size = size; - - dmr->time = intrin_timestamp_counter(); - dmr->function_name = function; -} - -// undo reserve -void debug_memory_free(uintptr_t start) -{ - if (!start || !debug_container) { - return; - } - - DebugMemory* mem = debug_memory_find(start); - if (!mem) { - return; - } - - for (int32 i = 0; i < ARRAY_COUNT(mem->reserve_action); ++i) { - DebugMemoryRange* dmr = &mem->reserve_action[i]; - if (dmr->start == start - mem->start) { - dmr->size = 0; - return; - } - } -} - -// @bug This probably requires thread safety -inline -void debug_memory_reset() -{ - if (!debug_container) { - return; - } - - // We remove debug information that are "older" than 1GHz - uint64 time = intrin_timestamp_counter() - 1 * GHZ; - - for (uint64 i = 0; i < debug_container->dmc.memory_element_idx; ++i) { - for (int32 j = 0; j < DEBUG_MEMORY_RANGE_MAX; ++j) { - if (debug_container->dmc.memory_stats[i].last_action[j].time < time) { - memset(&debug_container->dmc.memory_stats[i].last_action[j], 0, sizeof(DebugMemoryRange)); - } - } - } -} - -// @bug This probably requires thread safety -byte* log_get_memory() -{ - LogMemory* log_mem = &debug_container->log_memory; - if (log_mem->pos + MAX_LOG_LENGTH > log_mem->size) { - log_mem->pos = 0; - } - - byte* offset = (byte *) (log_mem->memory + log_mem->pos); - memset((void *) offset, 0, MAX_LOG_LENGTH); - - log_mem->pos += MAX_LOG_LENGTH; - - return offset; -} - -void log(const char* str, bool should_log, const char* file, const char* function, int32 line) -{ - if (!should_log || !debug_container) { - return; - } - - int64 len = str_length(str); - while (len > 0) { - LogMessage* msg = (LogMessage *) log_get_memory(); - - // Fill file - msg->file = file; - msg->function = function; - msg->line = line; - msg->message = (char *) (msg + 1); - msg->time = system_time(); - - int32 message_length = (int32) OMS_MIN(MAX_LOG_LENGTH - sizeof(LogMessage) - 1, len); - - memcpy(msg->message, str, message_length); - msg->message[message_length] = '\0'; - str += message_length; - len -= MAX_LOG_LENGTH - sizeof(LogMessage); - - if (debug_container->log_memory.size - debug_container->log_memory.pos < MAX_LOG_LENGTH) { - log_to_file(); - debug_container->log_memory.pos = 0; - } - } -} - -void log(const char* format, LogDataArray data, bool should_log, const char* file, const char* function, int32 line) -{ - ASSERT_SIMPLE(str_length(format) + str_length(file) + str_length(function) + 50 < MAX_LOG_LENGTH); - - if (!should_log || !debug_container) { - return; - } - - if (data.data[0].type == LOG_DATA_VOID) { - log(format, should_log, file, function, line); - return; - } - - LogMessage* msg = (LogMessage *) log_get_memory(); - msg->file = file; - msg->function = function; - msg->line = line; - msg->message = (char *) (msg + 1); - msg->time = system_time(); - - char temp_format[MAX_LOG_LENGTH]; - str_copy_short(msg->message, format); - - for (int32 i = 0; i < LOG_DATA_ARRAY; ++i) { - if (data.data[i].type == LOG_DATA_VOID) { - break; - } - - str_copy_short(temp_format, msg->message); - - switch (data.data[i].type) { - case LOG_DATA_NONE: { - } break; - case LOG_DATA_BYTE: { - sprintf_fast_iter(msg->message, temp_format, (int32) *((byte *) data.data[i].value)); - } break; - case LOG_DATA_INT32: { - sprintf_fast_iter(msg->message, temp_format, *((int32 *) data.data[i].value)); - } break; - case LOG_DATA_UINT32: { - sprintf_fast_iter(msg->message, temp_format, *((uint32 *) data.data[i].value)); - } break; - case LOG_DATA_INT64: { - sprintf_fast_iter(msg->message, temp_format, *((int64 *) data.data[i].value)); - } break; - case LOG_DATA_UINT64: { - sprintf_fast_iter(msg->message, temp_format, *((uint64 *) data.data[i].value)); - } break; - case LOG_DATA_CHAR: { - sprintf_fast_iter(msg->message, temp_format, *((char *) data.data[i].value)); - } break; - case LOG_DATA_CHAR_STR: { - sprintf_fast_iter(msg->message, temp_format, (const char *) data.data[i].value); - } break; - case LOG_DATA_FLOAT32: { - sprintf_fast_iter(msg->message, temp_format, *((f32 *) data.data[i].value)); - } break; - case LOG_DATA_FLOAT64: { - sprintf_fast_iter(msg->message, temp_format, *((f64 *) data.data[i].value)); - } break; - default: { - UNREACHABLE(); - } - } - } - - if (debug_container->log_memory.size - debug_container->log_memory.pos < MAX_LOG_LENGTH) { - log_to_file(); - debug_container->log_memory.pos = 0; - } -} - -#endif \ No newline at end of file diff --git a/log/Debug.h b/log/Debug.h deleted file mode 100644 index f6c263d..0000000 --- a/log/Debug.h +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Jingga - * - * @copyright Jingga - * @license OMS License 2.0 - * @version 1.0.0 - * @link https://jingga.app - */ -#ifndef TOS_LOG_DEBUG_H -#define TOS_LOG_DEBUG_H - -#include "../stdlib/Types.h" -#include "DebugMemory.h" -#include "TimingStat.h" -#include "../thread/Spinlock.h" - -#if _WIN32 - #include -#endif - -#ifndef MAX_LOG_LENGTH - #define MAX_LOG_LENGTH 256 -#endif - -#ifndef MAX_LOG_MESSAGES - #define MAX_LOG_MESSAGES 256 -#endif - -#ifndef LOG_LEVEL - #define LOG_LEVEL 0 -#endif - -#define LOG_DATA_ARRAY 5 - -#ifndef DEBUG_COUNTER - #define DEBUG_COUNTER 1 - enum DebugCounter { - DEBUG_COUNTER_MEM_ALLOC, - - DEBUG_COUNTER_DRIVE_READ, - DEBUG_COUNTER_DRIVE_WRITE, - - DEBUG_COUNTER_SIZE - }; -#endif - -enum LogDataType { - LOG_DATA_NONE, - LOG_DATA_VOID, - LOG_DATA_BYTE, - LOG_DATA_INT32, - LOG_DATA_UINT32, - LOG_DATA_INT64, - LOG_DATA_UINT64, - LOG_DATA_CHAR, - LOG_DATA_CHAR_STR, - LOG_DATA_FLOAT32, - LOG_DATA_FLOAT64 -}; - -struct LogMemory { - byte* memory; - - uint64 size; - uint64 pos; -}; - -struct LogMessage { - const char* file; - const char* function; - int32 line; - uint64 time; - char* message; -}; - -struct LogData { - LogDataType type; - void* value; -}; - -struct LogDataArray{ - LogData data[LOG_DATA_ARRAY]; -}; - -struct DebugContainer { - // Used to log memory access (read, write) - DebugMemoryContainer dmc; - LogMemory log_memory; - - // Used for logging timings for different sections - TimingStat* perf_stats; - spinlock32 perf_stats_spinlock; - - // Used to log general int values (e.g. counter for draw calls etc.) - int64* counter; - - // Required to calculate the "fps" - uint64 performance_count_frequency; - - // We are not using FileHandle here since that would require us to include more files - // These files in return require Debug.h - #if _WIN32 - HANDLE log_fp; - #elif __linux__ - int32 log_fp; - #endif -}; - -#endif \ No newline at end of file diff --git a/log/DebugContainer.h b/log/DebugContainer.h new file mode 100644 index 0000000..81f954d --- /dev/null +++ b/log/DebugContainer.h @@ -0,0 +1,34 @@ +/** + * Jingga + * + * @copyright Jingga + * @license OMS License 2.0 + * @version 1.0.0 + * @link https://jingga.app + */ +#ifndef TOS_LOG_DEBUG_CONTAINER_H +#define TOS_LOG_DEBUG_CONTAINER_H + +#include "Log.h" +#include "DebugMemory.h" +#include "PerformanceProfiler.h" +#include "Stats.h" + +struct DebugContainer { + DebugMemoryContainer* dmc; + + #if _WIN32 + HANDLE log_fp; + #elif __linux__ + int32 log_fp; + #endif + LogMemory* log_memory; + + PerformanceProfileResult* perf_stats; + + atomic_64 int64* stats_counter; + + PerformanceProfiler** perf_current_scope; +}; + +#endif \ No newline at end of file diff --git a/log/DebugMemory.h b/log/DebugMemory.h index 8c62257..47ebe04 100644 --- a/log/DebugMemory.h +++ b/log/DebugMemory.h @@ -10,9 +10,15 @@ #define TOS_LOG_DEBUG_MEMORY_H #include "../stdlib/Types.h" +#include "../thread/Atomic.h" -#define DEBUG_MEMORY_RANGE_MAX 500 -#define DEBUG_MEMORY_RANGE_RES_MAX 100 +#ifndef DEBUG_MEMORY_RANGE_MAX + #define DEBUG_MEMORY_RANGE_MAX 500 +#endif + +#ifndef DEBUG_MEMORY_RANGE_RES_MAX + #define DEBUG_MEMORY_RANGE_RES_MAX 100 +#endif struct DebugMemoryRange { int32 type; @@ -40,6 +46,7 @@ struct DebugMemoryContainer { uint64 memory_element_idx; DebugMemory* memory_stats; }; +static DebugMemoryContainer* _dmc = NULL; enum MemoryDebugType { MEMORY_DEBUG_TYPE_DELETE = -1, @@ -49,13 +56,151 @@ enum MemoryDebugType { MEMORY_DEBUG_TYPE_SUBREGION = 3, }; -#if DEBUG || INTERNAL - void debug_memory_init(uintptr_t, uint64); - void debug_memory_log(uintptr_t, uint64, int32, const char*); - void debug_memory_reserve(uintptr_t, uint64, int32, const char*); - void debug_memory_free(uintptr_t); - void debug_memory_reset(); +inline +DebugMemory* debug_memory_find(uintptr_t start) +{ + for (uint64 i = 0; i < _dmc->memory_size; ++i) { + if (_dmc->memory_stats[i].start <= start + && _dmc->memory_stats[i].start + _dmc->memory_stats[i].size >= start + ) { + return &_dmc->memory_stats[i]; + } + } + return NULL; +} + +void debug_memory_init(uintptr_t start, uint64 size) +{ + if (!start || !_dmc) { + return; + } + + const DebugMemory* mem = debug_memory_find(start); + if (mem) { + return; + } + + if (_dmc->memory_size <= _dmc->memory_element_idx) { + DebugMemory* old = _dmc->memory_stats; + + _dmc->memory_size += 3; + _dmc->memory_stats = (DebugMemory *) calloc(_dmc->memory_size, sizeof(DebugMemory)); + + if (old) { + memcpy(_dmc->memory_stats, old, (_dmc->memory_size - 3) * sizeof(DebugMemory)); + free(old); + } + } + + DebugMemory* debug_mem = &_dmc->memory_stats[_dmc->memory_element_idx]; + debug_mem->start = start; + debug_mem->size = size; + debug_mem->usage = 0; + + ++_dmc->memory_element_idx; +} + +void debug_memory_log(uintptr_t start, uint64 size, int32 type, const char* function) +{ + if (!start || !_dmc) { + return; + } + + DebugMemory* mem = debug_memory_find(start); + if (!mem) { + return; + } + + uint64 idx = atomic_fetch_add_relaxed(&mem->action_idx, 1); + if (idx >= ARRAY_COUNT(mem->last_action)) { + atomic_set_acquire(&mem->action_idx, 1); + idx %= ARRAY_COUNT(mem->last_action); + } + + DebugMemoryRange* dmr = &mem->last_action[idx]; + dmr->type = type; + dmr->start = start - mem->start; + dmr->size = size; + + dmr->time = intrin_timestamp_counter(); + dmr->function_name = function; + + if (type < 0 && mem->usage < size * -type) { + mem->usage = 0; + } else { + mem->usage += size * type; + } +} + +void debug_memory_reserve(uintptr_t start, uint64 size, int32 type, const char* function) +{ + if (!start || !_dmc) { + return; + } + + DebugMemory* mem = debug_memory_find(start); + if (!mem) { + return; + } + + uint64 idx = atomic_fetch_add_relaxed(&mem->reserve_action_idx, 1); + if (idx >= ARRAY_COUNT(mem->reserve_action)) { + atomic_set_acquire(&mem->reserve_action_idx, 1); + idx %= ARRAY_COUNT(mem->reserve_action); + } + + DebugMemoryRange* dmr = &mem->reserve_action[idx]; + dmr->type = type; + dmr->start = start - mem->start; + dmr->size = size; + + dmr->time = intrin_timestamp_counter(); + dmr->function_name = function; +} + +// undo reserve +void debug_memory_free(uintptr_t start) +{ + if (!start || !_dmc) { + return; + } + + DebugMemory* mem = debug_memory_find(start); + if (!mem) { + return; + } + + for (int32 i = 0; i < ARRAY_COUNT(mem->reserve_action); ++i) { + DebugMemoryRange* dmr = &mem->reserve_action[i]; + if (dmr->start == start - mem->start) { + dmr->size = 0; + return; + } + } +} + +// @bug This probably requires thread safety +inline +void debug_memory_reset() +{ + if (!_dmc) { + return; + } + + // We remove debug information that are "older" than 1GHz + uint64 time = intrin_timestamp_counter() - 1 * GHZ; + + for (uint64 i = 0; i < _dmc->memory_element_idx; ++i) { + for (int32 j = 0; j < DEBUG_MEMORY_RANGE_MAX; ++j) { + if (_dmc->memory_stats[i].last_action[j].time < time) { + memset(&_dmc->memory_stats[i].last_action[j], 0, sizeof(DebugMemoryRange)); + } + } + } +} + +#if DEBUG || INTERNAL #define DEBUG_MEMORY_INIT(start, size) debug_memory_init((start), (size)) #define DEBUG_MEMORY_READ(start, size) debug_memory_log((start), (size), MEMORY_DEBUG_TYPE_READ, __func__) #define DEBUG_MEMORY_WRITE(start, size) debug_memory_log((start), (size), MEMORY_DEBUG_TYPE_WRITE, __func__) diff --git a/log/Log.h b/log/Log.h index b66ceab..58fdab0 100644 --- a/log/Log.h +++ b/log/Log.h @@ -10,13 +10,215 @@ #define TOS_LOG_H #include "../stdlib/Types.h" -#include "Debug.h" +#include "../compiler/CompilerUtils.h" +#include "../utils/StringUtils.h" +#include "../platform/win32/TimeUtils.h" -void log_to_file(); -void log(const char* str, bool should_log, const char* file, const char* function, int32 line); -void log(const char* format, LogDataArray data, bool should_log, const char* file, const char* function, int32 line); -void log_increment(int32, int64); -void log_counter(int32, int64); +#define LOG_DATA_ARRAY 5 + +#ifndef LOG_LEVEL + #define LOG_LEVEL 0 +#endif + +#ifndef MAX_LOG_LENGTH + #define MAX_LOG_LENGTH 256 +#endif + +#ifndef MAX_LOG_MESSAGES + #define MAX_LOG_MESSAGES 256 +#endif + +#if _WIN32 + static HANDLE _log_fp; +#elif __linux__ + static int32 _log_fp; +#endif + +struct LogMemory { + byte* memory; + + uint64 size; + uint64 pos; +}; +static LogMemory* _log_memory = NULL; + +enum LogDataType { + LOG_DATA_NONE, + LOG_DATA_VOID, + LOG_DATA_BYTE, + LOG_DATA_INT32, + LOG_DATA_UINT32, + LOG_DATA_INT64, + LOG_DATA_UINT64, + LOG_DATA_CHAR, + LOG_DATA_CHAR_STR, + LOG_DATA_FLOAT32, + LOG_DATA_FLOAT64 +}; + +struct LogMessage { + const char* file; + const char* function; + int32 line; + uint64 time; + char* message; +}; + +struct LogData { + LogDataType type; + void* value; +}; + +struct LogDataArray{ + LogData data[LOG_DATA_ARRAY]; +}; + +// @bug This probably requires thread safety +byte* log_get_memory() +{ + if (_log_memory->pos + MAX_LOG_LENGTH > _log_memory->size) { + _log_memory->pos = 0; + } + + byte* offset = (byte *) (_log_memory->memory + _log_memory->pos); + memset((void *) offset, 0, MAX_LOG_LENGTH); + + _log_memory->pos += MAX_LOG_LENGTH; + + return offset; +} + +void log_to_file() +{ + // we don't log an empty log pool + if (!_log_memory || _log_memory->pos == 0 || !_log_fp) { + return; + } + + #if _WIN32 + DWORD written; + WriteFile( + _log_fp, + (char *) _log_memory->memory, + (uint32) _log_memory->pos, + &written, + NULL + ); + #else + if (_log_fp < 0) { + return; + } + + write( + _log_fp, + (char *) _log_memory->memory, + (uint32) _log_memory->pos + ); + #endif +} + +void log(const char* str, bool should_log, const char* file, const char* function, int32 line) +{ + if (!should_log || !_log_memory) { + return; + } + + int64 len = str_length(str); + while (len > 0) { + LogMessage* msg = (LogMessage *) log_get_memory(); + + // Fill file + msg->file = file; + msg->function = function; + msg->line = line; + msg->message = (char *) (msg + 1); + msg->time = system_time(); + + int32 message_length = (int32) OMS_MIN(MAX_LOG_LENGTH - sizeof(LogMessage) - 1, len); + + memcpy(msg->message, str, message_length); + msg->message[message_length] = '\0'; + str += message_length; + len -= MAX_LOG_LENGTH - sizeof(LogMessage); + + if (_log_memory->size - _log_memory->pos < MAX_LOG_LENGTH) { + log_to_file(); + _log_memory->pos = 0; + } + } +} + +void log(const char* format, LogDataArray data, bool should_log, const char* file, const char* function, int32 line) +{ + ASSERT_SIMPLE(str_length(format) + str_length(file) + str_length(function) + 50 < MAX_LOG_LENGTH); + + if (!should_log || !_log_memory) { + return; + } + + if (data.data[0].type == LOG_DATA_VOID) { + log(format, should_log, file, function, line); + return; + } + + LogMessage* msg = (LogMessage *) log_get_memory(); + msg->file = file; + msg->function = function; + msg->line = line; + msg->message = (char *) (msg + 1); + msg->time = system_time(); + + char temp_format[MAX_LOG_LENGTH]; + str_copy_short(msg->message, format); + + for (int32 i = 0; i < LOG_DATA_ARRAY; ++i) { + if (data.data[i].type == LOG_DATA_VOID) { + break; + } + + str_copy_short(temp_format, msg->message); + + switch (data.data[i].type) { + case LOG_DATA_NONE: { + } break; + case LOG_DATA_BYTE: { + sprintf_fast_iter(msg->message, temp_format, (int32) *((byte *) data.data[i].value)); + } break; + case LOG_DATA_INT32: { + sprintf_fast_iter(msg->message, temp_format, *((int32 *) data.data[i].value)); + } break; + case LOG_DATA_UINT32: { + sprintf_fast_iter(msg->message, temp_format, *((uint32 *) data.data[i].value)); + } break; + case LOG_DATA_INT64: { + sprintf_fast_iter(msg->message, temp_format, *((int64 *) data.data[i].value)); + } break; + case LOG_DATA_UINT64: { + sprintf_fast_iter(msg->message, temp_format, *((uint64 *) data.data[i].value)); + } break; + case LOG_DATA_CHAR: { + sprintf_fast_iter(msg->message, temp_format, *((char *) data.data[i].value)); + } break; + case LOG_DATA_CHAR_STR: { + sprintf_fast_iter(msg->message, temp_format, (const char *) data.data[i].value); + } break; + case LOG_DATA_FLOAT32: { + sprintf_fast_iter(msg->message, temp_format, *((f32 *) data.data[i].value)); + } break; + case LOG_DATA_FLOAT64: { + sprintf_fast_iter(msg->message, temp_format, *((f64 *) data.data[i].value)); + } break; + default: { + UNREACHABLE(); + } + } + } + + if (_log_memory->size - _log_memory->pos < MAX_LOG_LENGTH) { + log_to_file(); + _log_memory->pos = 0; + } +} #define LOG(should_log, str) log((str), (should_log), __FILE__, __func__, __LINE__) #define LOG_FORMAT(should_log, format, ...) log((format), LogDataArray{__VA_ARGS__}, (should_log), __FILE__, __func__, __LINE__) @@ -33,16 +235,4 @@ void log_counter(int32, int64); #define LOG_LEVEL_2(format, ...) ((void) 0) #endif -#if (!DEBUG && !INTERNAL) || RELEASE - #define LOG_INCREMENT(a) ((void) 0) - #define LOG_INCREMENT_BY(a, b) ((void) 0) - #define LOG_COUNTER(a, b) ((void) 0) - #define RESET_COUNTER(a) ((void) 0) -#else - #define LOG_INCREMENT(a) log_increment((a), 1) - #define LOG_INCREMENT_BY(a, b) log_increment((a), (b)) - #define LOG_COUNTER(a, b) log_counter((a), (b)) - #define RESET_COUNTER(a) reset_counter((a)) -#endif - #endif \ No newline at end of file diff --git a/log/PerformanceProfiler.h b/log/PerformanceProfiler.h new file mode 100644 index 0000000..3ad21e3 --- /dev/null +++ b/log/PerformanceProfiler.h @@ -0,0 +1,150 @@ +/** + * Jingga + * + * @copyright Jingga + * @license OMS License 2.0 + * @version 1.0.0 + * @link https://jingga.app + */ +#ifndef TOS_LOG_PERFORMANCE_PROFILER_H +#define TOS_LOG_PERFORMANCE_PROFILER_H + +#include "../stdlib/Types.h" +#include "../platform/win32/TimeUtils.h" +#include "../architecture/Intrinsics.h" +#include "../compiler/CompilerUtils.h" + +struct PerformanceProfileResult { + const char* name; + atomic_64 int64 total_time; + atomic_64 int64 total_cycle; + atomic_64 int64 self_time; + atomic_64 int64 self_cycle; + + // Required for manual start/stop otherwise we would have to use one of the existing values above, + // which corrupts them for rendering + atomic_64 int64 tmp_time; + atomic_64 int64 tmp_cycle; + PerformanceProfileResult* parent; +}; +static PerformanceProfileResult* _perf_stats = NULL; + +struct PerformanceProfiler; +static PerformanceProfiler** _perf_current_scope = NULL; // Used when sharing profiler across dlls and threads (threads unlikely) +static PerformanceProfiler* _perf_current_scope_internal; // Used when in dll or thread and no shared pointer found +struct PerformanceProfiler { + const char* name; + int32 id; + + int64 start_time; + int64 total_time; + int64 self_time; + + int64 start_cycle; + int64 total_cycle; + int64 self_cycle; + + PerformanceProfiler* parent; + + PerformanceProfiler(int32 id, const char* scope_name) : id(id) { + name = scope_name; + + start_time = time_mu(); + start_cycle = intrin_timestamp_counter(); + + total_time = 0; + total_cycle = 0; + + self_time = 0; + self_cycle = 0; + + if (_perf_current_scope) { + parent = *_perf_current_scope; + *_perf_current_scope = this; + } else { + parent = _perf_current_scope_internal; + _perf_current_scope_internal = this; + } + } + + ~PerformanceProfiler() { + uint64 end_time = time_mu(); + uint64 end_cycle = intrin_timestamp_counter(); + + total_time = OMS_MAX(end_time - start_time, 0); + total_cycle = OMS_MAX(end_cycle - start_cycle, 0); + + self_time += total_time; + self_cycle += total_cycle; + + if (parent) { + parent->self_time -= total_time; + parent->self_cycle -= total_cycle; + } + + // Store result + PerformanceProfileResult* perf = &_perf_stats[id]; + perf->name = name; + perf->total_time = total_time; + perf->total_cycle = total_cycle; + perf->self_time = self_time; + perf->self_cycle = self_cycle; + // @todo create reference to parent result + + if (_perf_current_scope) { + *_perf_current_scope = parent; + } else { + _perf_current_scope_internal = parent; + } + } +}; + +void performance_profiler_reset(uint32 id) +{ + PerformanceProfileResult* perf = &_perf_stats[id]; + perf->total_time = 0; + perf->total_cycle = 0; + perf->self_time = 0; + perf->self_cycle = 0; +} + +void performance_profiler_start(uint32 id, const char* name) +{ + PerformanceProfileResult* perf = &_perf_stats[id]; + perf->name = name; + + perf->tmp_time = time_mu(); + perf->tmp_cycle = intrin_timestamp_counter(); +} + +void performance_profiler_end(uint32 id) +{ + PerformanceProfileResult* perf = &_perf_stats[id]; + perf->total_time = time_mu() - perf->tmp_time; + perf->total_cycle = intrin_timestamp_counter() - perf->tmp_cycle; + perf->self_time = perf->total_time; + perf->self_cycle = perf->self_cycle; +} + +#if (!DEBUG && !INTERNAL) || RELEASE + #define PROFILE_SCOPE(id, name) ((void) 0) + #define PROFILE(id) ((void) 0) + + #define PROFILE_START(id, name) ((void) 0) + #define PROFILE_END(id) ((void) 0) + + #define PROFILE_RESET(id) ((void) 0) +#else + #define PROFILE_SCOPE(id, name) PerformanceProfiler __profile_scope_##id(id, name) + + // Only this function can properly handle self-time calculation + // Use this whenever you want to profile an entire function + #define PROFILE(id) PROFILE_SCOPE(id, __func__) + + #define PROFILE_START(id, name) performance_profiler_start(id, name) + #define PROFILE_END(id) performance_profiler_end(id) + + #define PROFILE_RESET(id) performance_profiler_reset((id)) +#endif + +#endif \ No newline at end of file diff --git a/log/Stats.h b/log/Stats.h new file mode 100644 index 0000000..0e9a74d --- /dev/null +++ b/log/Stats.h @@ -0,0 +1,64 @@ + +#ifndef TOS_LOG_STATS_H +#define TOS_LOG_STATS_H + +#include "../stdlib/Types.h" +#include "../thread/Atomic.h" + +#ifndef DEBUG_COUNTER + #define DEBUG_COUNTER 1 + enum DebugCounter { + DEBUG_COUNTER_MEM_ALLOC, + + DEBUG_COUNTER_DRIVE_READ, + DEBUG_COUNTER_DRIVE_WRITE, + + DEBUG_COUNTER_SIZE + }; +#endif + +static atomic_64 int64* _stats_counter = NULL; + +inline +void reset_counter(int32 id) +{ + if (!_stats_counter) { + return; + } + + atomic_set_acquire(&_stats_counter[id], 0); +} + +inline +void log_increment(int32 id, int64 by = 1) +{ + if (!_stats_counter) { + return; + } + + atomic_add_acquire(&_stats_counter[id], by); +} + +inline +void log_counter(int32 id, int64 value) +{ + if (!_stats_counter) { + return; + } + + atomic_set_acquire(&_stats_counter[id], value); +} + +#if (!DEBUG && !INTERNAL) || RELEASE + #define LOG_INCREMENT(a) ((void) 0) + #define LOG_INCREMENT_BY(a, b) ((void) 0) + #define LOG_COUNTER(a, b) ((void) 0) + #define RESET_COUNTER(a) ((void) 0) +#else + #define LOG_INCREMENT(a) log_increment((a), 1) + #define LOG_INCREMENT_BY(a, b) log_increment((a), (b)) + #define LOG_COUNTER(a, b) log_counter((a), (b)) + #define RESET_COUNTER(a) reset_counter((a)) +#endif + +#endif \ No newline at end of file diff --git a/log/TimingStat.h b/log/TimingStat.h deleted file mode 100644 index 16c9116..0000000 --- a/log/TimingStat.h +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Jingga - * - * @copyright Jingga - * @license OMS License 2.0 - * @version 1.0.0 - * @link https://jingga.app - */ -#ifndef TOS_LOG_TIMING_STAT_H -#define TOS_LOG_TIMING_STAT_H - -#include "../stdlib/Types.h" -#include "Debug.h" - -#if _WIN32 - #include -#else - #include -#endif - -struct TimingStat { - const char* function; - uint64 old_tick_count; - f64 delta_time; - uint32 delta_tick; -}; - -// Sometimes we want to only do logging in debug mode. -// In such cases use the following macro. -#if DEBUG || INTERNAL - void update_timing_stat(uint32, const char*); - #define UPDATE_TIMING_STAT(stat) update_timing_stat(stat, __func__) - - // These are only needed if we need to delay the overwrite by 1 frame (e.g. ui update) - void update_timing_stat_start(uint32, const char*); - #define UPDATE_TIMING_STAT_START(stat) update_timing_stat_start(stat, __func__) - #define UPDATE_TIMING_STAT_CONTINUE(stat) update_timing_stat_start(stat, __func__) - - void update_timing_stat_end(uint32, const char*); - #define UPDATE_TIMING_STAT_END(stat) update_timing_stat_end(stat, __func__) - - void update_timing_stat_end_continued(uint32, const char*); - #define UPDATE_TIMING_STAT_END_CONTINUED(stat) update_timing_stat_end_continued(stat, __func__) - - void update_timing_stat_reset(uint32); - #define UPDATE_TIMING_STAT_RESET(stat) update_timing_stat_reset(stat) -#else - #define UPDATE_TIMING_STAT(stat) ((void) 0) - #define UPDATE_TIMING_STAT_START(stat) ((void) 0) - #define UPDATE_TIMING_STAT_END(stat) ((void) 0) - #define UPDATE_TIMING_STAT_CONTINUE(stat) ((void) 0) - #define UPDATE_TIMING_STAT_END_CONTINUED(stat) ((void) 0) - #define UPDATE_TIMING_STAT_RESET(stat) ((void) 0) -#endif - -#endif \ No newline at end of file diff --git a/math/Evaluator.h b/math/Evaluator.h index c0a6274..0180bae 100644 --- a/math/Evaluator.h +++ b/math/Evaluator.h @@ -267,6 +267,8 @@ f32 evaluator_evaluate_function(const char* name, const char* args) { } f32 evaluator_evaluate(const char* expr, int32 variable_count = 0, const EvaluatorVariable* variables = NULL) { + ASSERT_SIMPLE(str_length(expr) > 0); + // Handle variables char expr_internal[1024]; char* dest = expr_internal; diff --git a/memory/BufferMemory.h b/memory/BufferMemory.h index 23a9c21..2d6cfdc 100644 --- a/memory/BufferMemory.h +++ b/memory/BufferMemory.h @@ -14,6 +14,7 @@ #include "../utils/EndianUtils.h" #include "../utils/TestUtils.h" #include "../log/Log.h" +#include "../log/Stats.h" #include "../log/DebugMemory.h" #include "../system/Allocator.h" diff --git a/memory/ChunkMemory.h b/memory/ChunkMemory.h index fb23a3c..2b69e8b 100644 --- a/memory/ChunkMemory.h +++ b/memory/ChunkMemory.h @@ -14,8 +14,10 @@ #include "../utils/TestUtils.h" #include "../utils/EndianUtils.h" #include "../utils/BitUtils.h" +#include "../compiler/CompilerUtils.h" #include "../log/Log.h" -#include "../log/Debug.cpp" +#include "../log/Stats.h" +#include "../log/DebugMemory.h" #include "BufferMemory.h" #include "../system/Allocator.h" #include "../thread/Thread.h" @@ -35,6 +37,7 @@ struct ChunkMemory { uint64* free; }; +// INFO: A chunk count of 2^n is recommended for maximum performance inline void chunk_alloc(ChunkMemory* buf, uint32 count, uint32 chunk_size, int32 alignment = 64) { @@ -153,26 +156,38 @@ byte* chunk_get_element(ChunkMemory* buf, uint64 element, bool zeroed = false) return offset; } -// @performance This is a very important function, revisit in the future for optimization (e.g. ABM) -// @performance Is _BitScanForward faster? -// @performance We could probably even reduce the number of iterations by only iterating until popcount is reached? int32 chunk_reserve(ChunkMemory* buf, uint32 elements = 1) { + if ((uint32) (buf->last_pos + 1) >= buf->count) { + buf->last_pos = -1; + } + uint32 free_index = (buf->last_pos + 1) / 64; uint32 bit_index = (buf->last_pos + 1) & 63; - int32 free_element = -1; + // Check standard simple solution + if (elements == 1 && !IS_BIT_SET_64_R2L(buf->free[free_index], bit_index)) { + buf->free[free_index] |= (1ULL << bit_index); + ++buf->last_pos; + + return free_index * 64 + bit_index; + } + + int32 free_element = -1; uint32 i = 0; uint32 consecutive_free_bits = 0; while (free_element < 0 && i++ <= buf->count) { - // Skip fully filled ranges if (free_index * 64 + bit_index + elements - consecutive_free_bits > buf->count) { + // Go to beginning after overflow i += buf->count - (free_index * 64 + bit_index); consecutive_free_bits = 0; free_index = 0; bit_index = 0; + + continue; } else if (buf->free[free_index] == 0xFFFFFFFFFFFFFFFF) { + // Skip fully filled ranges ++free_index; bit_index = 0; i += 64; @@ -182,28 +197,10 @@ int32 chunk_reserve(ChunkMemory* buf, uint32 elements = 1) } // Find first free element - while (IS_BIT_SET_64_R2L(buf->free[free_index], bit_index) && i <= buf->count) { - consecutive_free_bits = 0; - ++bit_index; - ++i; + // This MUST find a free element, otherwise we wouldn't have gotten here + bit_index = compiler_find_first_bit_r2l(~buf->free[free_index]); - // We still need to check for overflow since our initial bit_index is based on buf->last_pos - if (bit_index > 63) { - bit_index = 0; - ++free_index; - - break; - } - } - - // The previous while may exit with an "overflow", that's why this check is required - if (IS_BIT_SET_64_R2L(buf->free[free_index], bit_index) || i > buf->count) { - consecutive_free_bits = 0; - - continue; - } - - // We found our first free element, let's check if we have enough free space + // Let's check if we have enough free space, we need more than just one free bit do { ++i; ++consecutive_free_bits; diff --git a/memory/RingMemory.h b/memory/RingMemory.h index 898368f..b8a2d7c 100644 --- a/memory/RingMemory.h +++ b/memory/RingMemory.h @@ -18,7 +18,8 @@ #include "BufferMemory.h" #include "../log/Log.h" -#include "../log/Debug.cpp" +#include "../log/Stats.h" +#include "../log/DebugMemory.h" #include "../thread/Atomic.h" #include "../thread/Semaphore.h" #include "../thread/ThreadDefines.h" diff --git a/platform/linux/threading/ThreadDefines.h b/platform/linux/threading/ThreadDefines.h index f5119d8..e5ad3f2 100644 --- a/platform/linux/threading/ThreadDefines.h +++ b/platform/linux/threading/ThreadDefines.h @@ -44,4 +44,7 @@ typedef void pthread_mutexattr_t; typedef void pthread_condattr_t; typedef void pthread_rwlockattr_t; +// Thread local variable Already exists in c++11 +// #define thread_local __thread + #endif \ No newline at end of file diff --git a/platform/win32/ExceptionHandler.h b/platform/win32/ExceptionHandler.h index a136aa3..43b93e7 100644 --- a/platform/win32/ExceptionHandler.h +++ b/platform/win32/ExceptionHandler.h @@ -13,7 +13,7 @@ #include #include #include -#include "../../log/Debug.cpp" +#include "../../log/Log.h" #ifdef _MSC_VER #pragma comment(lib, "dbghelp.lib") diff --git a/platform/win32/FileUtils.cpp b/platform/win32/FileUtils.cpp index 4c90c9c..a87f621 100644 --- a/platform/win32/FileUtils.cpp +++ b/platform/win32/FileUtils.cpp @@ -21,7 +21,7 @@ #include "../../utils/Utils.h" #include "../../utils/TestUtils.h" #include "../../memory/RingMemory.h" -#include "../../log/Log.h" +#include "../../log/Stats.h" typedef HANDLE FileHandle; typedef HANDLE MMFHandle; diff --git a/platform/win32/UtilsWindows.h b/platform/win32/UtilsWindows.h index 603da59..b486035 100644 --- a/platform/win32/UtilsWindows.h +++ b/platform/win32/UtilsWindows.h @@ -149,4 +149,22 @@ void window_close(Window* window) DestroyWindow(window->hwnd); } +HBITMAP CreateBitmapFromRGBA(HDC hdc, const byte* rgba, int32 width, int32 height) { + BITMAPINFO bmi = {}; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + + void* pbits; + HBITMAP hBitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &pbits, NULL, 0); + if (hBitmap) { + memcpy(pbits, rgba, width * height * 4); + } + + return hBitmap; +} + #endif \ No newline at end of file diff --git a/platform/win32/threading/ThreadDefines.h b/platform/win32/threading/ThreadDefines.h index 2d29868..b9b4c53 100644 --- a/platform/win32/threading/ThreadDefines.h +++ b/platform/win32/threading/ThreadDefines.h @@ -21,6 +21,9 @@ typedef void pthread_rwlockattr_t; typedef HANDLE pthread_t; typedef CONDITION_VARIABLE pthread_cond_t; +// Thread local variable Already exists in c++11 +// #define thread_local __declspec(thread) + struct pthread_rwlock_t { SRWLOCK lock; bool exclusive; diff --git a/stdlib/HashMap.h b/stdlib/HashMap.h index 2257a6e..228289d 100644 --- a/stdlib/HashMap.h +++ b/stdlib/HashMap.h @@ -225,16 +225,12 @@ void hashmap_insert(HashMap* hm, const char* key, int32 value) { entry->value = value; entry->next = 0; - if (hm->table[index]) { - HashEntryInt32* tmp = (HashEntryInt32 *) chunk_get_element(&hm->buf, hm->table[index] - 1, false); - while(tmp->next) { - tmp = (HashEntryInt32 *) chunk_get_element(&hm->buf, tmp->next - 1, false); - } - - tmp->next = (uint16) (element + 1); - } else { - hm->table[index] = (uint16) (element + 1); + uint16* target = &hm->table[index]; + while (*target) { + HashEntryInt32* tmp = (HashEntryInt32*) chunk_get_element(&hm->buf, *target - 1, false); + target = &tmp->next; } + *target = (uint16) (element + 1); } void hashmap_insert(HashMap* hm, const char* key, int64 value) { @@ -251,16 +247,12 @@ void hashmap_insert(HashMap* hm, const char* key, int64 value) { entry->value = value; entry->next = 0; - if (hm->table[index]) { - HashEntryInt64* tmp = (HashEntryInt64 *) chunk_get_element(&hm->buf, hm->table[index] - 1, false); - while(tmp->next) { - tmp = (HashEntryInt64 *) chunk_get_element(&hm->buf, tmp->next - 1, false); - } - - tmp->next = (uint16) (element + 1); - } else { - hm->table[index] = (uint16) (element + 1); + uint16* target = &hm->table[index]; + while (*target) { + HashEntryInt64* tmp = (HashEntryInt64*) chunk_get_element(&hm->buf, *target - 1, false); + target = &tmp->next; } + *target = (uint16) (element + 1); } void hashmap_insert(HashMap* hm, const char* key, uintptr_t value) { @@ -277,16 +269,12 @@ void hashmap_insert(HashMap* hm, const char* key, uintptr_t value) { entry->value = value; entry->next = 0; - if (hm->table[index]) { - HashEntryUIntPtr* tmp = (HashEntryUIntPtr *) chunk_get_element(&hm->buf, hm->table[index] - 1, false); - while(tmp->next) { - tmp = (HashEntryUIntPtr *) chunk_get_element(&hm->buf, tmp->next - 1, false); - } - - tmp->next = (uint16) (element + 1); - } else { - hm->table[index] = (uint16) (element + 1); + uint16* target = &hm->table[index]; + while (*target) { + HashEntryUIntPtr* tmp = (HashEntryUIntPtr*) chunk_get_element(&hm->buf, *target - 1, false); + target = &tmp->next; } + *target = (uint16) (element + 1); } void hashmap_insert(HashMap* hm, const char* key, void* value) { @@ -303,16 +291,12 @@ void hashmap_insert(HashMap* hm, const char* key, void* value) { entry->value = value; entry->next = 0; - if (hm->table[index]) { - HashEntryVoidP* tmp = (HashEntryVoidP *) chunk_get_element(&hm->buf, hm->table[index] - 1, false); - while(tmp->next) { - tmp = (HashEntryVoidP *) chunk_get_element(&hm->buf, tmp->next - 1, false); - } - - tmp->next = (uint16) (element + 1); - } else { - hm->table[index] = (uint16) (element + 1); + uint16* target = &hm->table[index]; + while (*target) { + HashEntryVoidP* tmp = (HashEntryVoidP*) chunk_get_element(&hm->buf, *target - 1, false); + target = &tmp->next; } + *target = (uint16) (element + 1); } void hashmap_insert(HashMap* hm, const char* key, f32 value) { @@ -329,16 +313,12 @@ void hashmap_insert(HashMap* hm, const char* key, f32 value) { entry->value = value; entry->next = 0; - if (hm->table[index]) { - HashEntryFloat* tmp = (HashEntryFloat *) chunk_get_element(&hm->buf, hm->table[index] - 1, false); - while(tmp->next) { - tmp = (HashEntryFloat *) chunk_get_element(&hm->buf, tmp->next - 1, false); - } - - tmp->next = (uint16) (element + 1); - } else { - hm->table[index] = (uint16) (element + 1); + uint16* target = &hm->table[index]; + while (*target) { + HashEntryFloat* tmp = (HashEntryFloat*) chunk_get_element(&hm->buf, *target - 1, false); + target = &tmp->next; } + *target = (uint16) (element + 1); } void hashmap_insert(HashMap* hm, const char* key, const char* value) { @@ -357,16 +337,12 @@ void hashmap_insert(HashMap* hm, const char* key, const char* value) { entry->next = 0; - if (hm->table[index]) { - HashEntryStr* tmp = (HashEntryStr *) chunk_get_element(&hm->buf, hm->table[index] - 1, false); - while(tmp->next) { - tmp = (HashEntryStr *) chunk_get_element(&hm->buf, tmp->next - 1, false); - } - - tmp->next = (uint16) (element + 1); - } else { - hm->table[index] = (uint16) (element + 1); + uint16* target = &hm->table[index]; + while (*target) { + HashEntryStr* tmp = (HashEntryStr*) chunk_get_element(&hm->buf, *target - 1, false); + target = &tmp->next; } + *target = (uint16) (element + 1); } HashEntry* hashmap_insert(HashMap* hm, const char* key, byte* value) { @@ -386,16 +362,12 @@ HashEntry* hashmap_insert(HashMap* hm, const char* key, byte* value) { entry->next = 0; - if (hm->table[index]) { - HashEntry* tmp = (HashEntry *) chunk_get_element(&hm->buf, hm->table[index] - 1, false); - while(tmp->next) { - tmp = (HashEntry *) chunk_get_element(&hm->buf, tmp->next - 1, false); - } - - tmp->next = (uint16) (element + 1); - } else { - hm->table[index] = (uint16) (element + 1); + uint16* target = &hm->table[index]; + while (*target) { + HashEntry* tmp = (HashEntry*) chunk_get_element(&hm->buf, *target - 1, false); + target = &tmp->next; } + *target = (uint16) (element + 1); return entry; } @@ -415,16 +387,12 @@ HashEntry* hashmap_reserve(HashMap* hm, const char* key) { entry->next = 0; - if (hm->table[index]) { - HashEntry* tmp = (HashEntry *) chunk_get_element(&hm->buf, hm->table[index] - 1, false); - while(tmp->next) { - tmp = (HashEntry *) chunk_get_element(&hm->buf, tmp->next - 1, false); - } - - tmp->next = (uint16) (element + 1); - } else { - hm->table[index] = (uint16) (element + 1); + uint16* target = &hm->table[index]; + while (*target) { + HashEntry* tmp = (HashEntry*) chunk_get_element(&hm->buf, *target - 1, false); + target = &tmp->next; } + *target = (uint16) (element + 1); return entry; } @@ -575,16 +543,12 @@ void hashmap_insert(HashMap* hm, int32 key, int32 value) { entry->value = value; entry->next = 0; - if (hm->table[index]) { - HashEntryInt32KeyInt32* tmp = (HashEntryInt32KeyInt32 *) chunk_get_element(&hm->buf, hm->table[index] - 1, false); - while(tmp->next) { - tmp = (HashEntryInt32KeyInt32 *) chunk_get_element(&hm->buf, tmp->next - 1, false); - } - - tmp->next = (uint16) (element + 1); - } else { - hm->table[index] = (uint16) (element + 1); + uint16* target = &hm->table[index]; + while (*target) { + HashEntryInt32KeyInt32* tmp = (HashEntryInt32KeyInt32*) chunk_get_element(&hm->buf, *target - 1, false); + target = &tmp->next; } + *target = (uint16) (element + 1); } void hashmap_insert(HashMap* hm, int32 key, int64 value) { @@ -597,16 +561,12 @@ void hashmap_insert(HashMap* hm, int32 key, int64 value) { entry->value = value; entry->next = 0; - if (hm->table[index]) { - HashEntryInt64KeyInt32* tmp = (HashEntryInt64KeyInt32 *) chunk_get_element(&hm->buf, hm->table[index] - 1, false); - while(tmp->next) { - tmp = (HashEntryInt64KeyInt32 *) chunk_get_element(&hm->buf, tmp->next - 1, false); - } - - tmp->next = (uint16) (element + 1); - } else { - hm->table[index] = (uint16) (element + 1); + uint16* target = &hm->table[index]; + while (*target) { + HashEntryInt64KeyInt32* tmp = (HashEntryInt64KeyInt32*) chunk_get_element(&hm->buf, *target - 1, false); + target = &tmp->next; } + *target = (uint16) (element + 1); } void hashmap_insert(HashMap* hm, int32 key, uintptr_t value) { @@ -619,16 +579,12 @@ void hashmap_insert(HashMap* hm, int32 key, uintptr_t value) { entry->value = value; entry->next = 0; - if (hm->table[index]) { - HashEntryUIntPtrKeyInt32* tmp = (HashEntryUIntPtrKeyInt32 *) chunk_get_element(&hm->buf, hm->table[index] - 1, false); - while(tmp->next) { - tmp = (HashEntryUIntPtrKeyInt32 *) chunk_get_element(&hm->buf, tmp->next - 1, false); - } - - tmp->next = (uint16) (element + 1); - } else { - hm->table[index] = (uint16) (element + 1); + uint16* target = &hm->table[index]; + while (*target) { + HashEntryUIntPtrKeyInt32* tmp = (HashEntryUIntPtrKeyInt32*) chunk_get_element(&hm->buf, *target - 1, false); + target = &tmp->next; } + *target = (uint16) (element + 1); } void hashmap_insert(HashMap* hm, int32 key, void* value) { @@ -641,16 +597,12 @@ void hashmap_insert(HashMap* hm, int32 key, void* value) { entry->value = value; entry->next = 0; - if (hm->table[index]) { - HashEntryVoidPKeyInt32* tmp = (HashEntryVoidPKeyInt32 *) chunk_get_element(&hm->buf, hm->table[index] - 1, false); - while(tmp->next) { - tmp = (HashEntryVoidPKeyInt32 *) chunk_get_element(&hm->buf, tmp->next - 1, false); - } - - tmp->next = (uint16) (element + 1); - } else { - hm->table[index] = (uint16) (element + 1); + uint16* target = &hm->table[index]; + while (*target) { + HashEntryVoidPKeyInt32* tmp = (HashEntryVoidPKeyInt32*) chunk_get_element(&hm->buf, *target - 1, false); + target = &tmp->next; } + *target = (uint16) (element + 1); } void hashmap_insert(HashMap* hm, int32 key, f32 value) { @@ -663,16 +615,12 @@ void hashmap_insert(HashMap* hm, int32 key, f32 value) { entry->value = value; entry->next = 0; - if (hm->table[index]) { - HashEntryFloatKeyInt32* tmp = (HashEntryFloatKeyInt32 *) chunk_get_element(&hm->buf, hm->table[index] - 1, false); - while(tmp->next) { - tmp = (HashEntryFloatKeyInt32 *) chunk_get_element(&hm->buf, tmp->next - 1, false); - } - - tmp->next = (uint16) (element + 1); - } else { - hm->table[index] = (uint16) (element + 1); + uint16* target = &hm->table[index]; + while (*target) { + HashEntryFloatKeyInt32* tmp = (HashEntryFloatKeyInt32*) chunk_get_element(&hm->buf, *target - 1, false); + target = &tmp->next; } + *target = (uint16) (element + 1); } void hashmap_insert(HashMap* hm, int32 key, const char* value) { @@ -688,16 +636,12 @@ void hashmap_insert(HashMap* hm, int32 key, const char* value) { entry->next = 0; - if (hm->table[index]) { - HashEntryStrKeyInt32* tmp = (HashEntryStrKeyInt32 *) chunk_get_element(&hm->buf, hm->table[index] - 1, false); - while(tmp->next) { - tmp = (HashEntryStrKeyInt32 *) chunk_get_element(&hm->buf, tmp->next - 1, false); - } - - tmp->next = (uint16) (element + 1); - } else { - hm->table[index] = (uint16) (element + 1); + uint16* target = &hm->table[index]; + while (*target) { + HashEntryStrKeyInt32* tmp = (HashEntryStrKeyInt32*) chunk_get_element(&hm->buf, *target - 1, false); + target = &tmp->next; } + *target = (uint16) (element + 1); } void hashmap_insert(HashMap* hm, int32 key, byte* value) { @@ -713,16 +657,12 @@ void hashmap_insert(HashMap* hm, int32 key, byte* value) { entry->next = 0; - if (hm->table[index]) { - HashEntryKeyInt32* tmp = (HashEntryKeyInt32 *) chunk_get_element(&hm->buf, hm->table[index] - 1, false); - while(tmp->next) { - tmp = (HashEntryKeyInt32 *) chunk_get_element(&hm->buf, tmp->next - 1, false); - } - - tmp->next = (uint16) (element + 1); - } else { - hm->table[index] = (uint16) (element + 1); + uint16* target = &hm->table[index]; + while (*target) { + HashEntryKeyInt32* tmp = (HashEntryKeyInt32*) chunk_get_element(&hm->buf, *target - 1, false); + target = &tmp->next; } + *target = (uint16) (element + 1); } HashEntryKeyInt32* hashmap_get_entry(HashMap* hm, int32 key) { @@ -869,14 +809,14 @@ inline int64 hashmap_load(HashMap* hm, const byte* data, [[maybe_unused]] int32 steps = 8) { uint64 count = SWAP_ENDIAN_LITTLE(*((uint32 *) data)); - data += sizeof(uint16); + data += sizeof(uint32); // Load the table content memcpy(hm->table, data, sizeof(uint16) * count); SWAP_ENDIAN_LITTLE_SIMD( - (uint32 *) hm->table, - (uint32 *) hm->table, - sizeof(uint16) * count / 4, // everything is 4 bytes -> super easy to swap + (uint16 *) hm->table, + (uint16 *) hm->table, + sizeof(uint16) * count / 2, // everything is 2 bytes -> super easy to swap steps ); data += sizeof(uint16) * count; diff --git a/tests/.vscode/settings.json b/tests/.vscode/settings.json index 0843d8b..3ce2dc5 100644 --- a/tests/.vscode/settings.json +++ b/tests/.vscode/settings.json @@ -230,7 +230,8 @@ "uilayout.h": "c", "camera.h": "c", "stdio.h": "c", - "uilabel.h": "c" + "uilabel.h": "c", + "uiattributedimension.h": "c" }, "C_Cpp.default.cppStandard": "c++20", "todo-tree.general.tags": [ diff --git a/tests/TestFramework.h b/tests/TestFramework.h index 379a8e9..eb045f8 100644 --- a/tests/TestFramework.h +++ b/tests/TestFramework.h @@ -46,6 +46,7 @@ double test_measure_func_time_ns(void (*func)(void *), void *para) #define TEST_INIT(test_count) \ do \ { \ + setvbuf(stdout, NULL, _IONBF, 0); \ SetUnhandledExceptionFilter(test_exception_handler); \ _test_assert_error_count = 0; \ _test_count = 0; \ @@ -81,6 +82,7 @@ double test_measure_func_time_ns(void (*func)(void *), void *para) #define TEST_INIT(test_count) \ do \ { \ + setvbuf(stdout, NULL, _IONBF, 0); \ signal(SIGSEGV, test_exception_handler); \ signal(SIGABRT, test_exception_handler); \ _test_assert_error_count = 0; \ @@ -103,19 +105,21 @@ double test_measure_func_time_ns(void (*func)(void *), void *para) if (_test_assert_error_count) \ { \ printf( \ - "[NG] %5d (%5d/%5d) %s\n", \ + "[NG] %5d (%5d/%5d) %s\n", \ _test_count, _test_assert_count - _test_assert_error_count, _test_assert_count, __FILE__); \ for (int i = 0; i < _test_assert_error_count; ++i) \ { \ - printf(" %s\n", _test_log[i]); \ + printf(" %s\n", _test_log[i]); \ + fflush(stdout); \ } \ } \ else \ { \ printf( \ - "[OK] %5d (%5d/%5d) %s\n", \ + "[OK] %5d (%5d/%5d) %s\n", \ _test_count, _test_assert_count - _test_assert_error_count, _test_assert_count, __FILE__); \ } \ + fflush(stdout); \ free(_test_log); \ _test_log = NULL; \ _test_assert_error_count = 0; \ @@ -156,18 +160,18 @@ double test_measure_func_time_ns(void (*func)(void *), void *para) } \ } while (0) -#define ASSERT_EQUALS_WITH_DELTA(a, b, delta) \ - do \ - { \ - ++_test_assert_count; \ - ++_test_global_assert_count; \ - if (OMS_ABS((a) - (b)) > (delta)) \ - { \ - ++_test_global_assert_error_count; \ - snprintf( \ - _test_log[_test_assert_error_count++], 1024, \ - "%4i: %lld != %lld", __LINE__, (int64)(a), (int64)(b)); \ - } \ +#define ASSERT_EQUALS_WITH_DELTA(a, b, delta) \ + do \ + { \ + ++_test_assert_count; \ + ++_test_global_assert_count; \ + if (OMS_ABS((a) - (b)) > (delta)) \ + { \ + ++_test_global_assert_error_count; \ + snprintf( \ + _test_log[_test_assert_error_count++], 1024, \ + "%4i: %f != %f", __LINE__, (f64)(a), (f64)(b)); \ + } \ } while (0) #define ASSERT_CONTAINS(a, b) \ @@ -240,6 +244,25 @@ double test_measure_func_time_ns(void (*func)(void *), void *para) } \ } while (0) +#define ASSERT_MEMORY_EQUALS(ptr1, ptr2, len) \ + do \ + { \ + ++_test_assert_count; \ + ++_test_global_assert_count; \ + const uint8_t *p1 = (const uint8_t *)(ptr1); \ + const uint8_t *p2 = (const uint8_t *)(ptr2); \ + for (uint64_t i = 0; i < (uint64_t)(len); ++i) \ + { \ + if (p1[i] != p2[i]) \ + { \ + ++_test_global_assert_error_count; \ + snprintf(_test_log[_test_global_assert_error_count], 1024, \ + "%4i: mismatch at offset %lld", __LINE__, i); \ + break; \ + } \ + } \ + } while (0) + #define COMPARE_FUNCTION_TEST_CYCLE(func1, func2, x_percent) \ do \ { \ @@ -247,7 +270,7 @@ double test_measure_func_time_ns(void (*func)(void *), void *para) ++_test_global_assert_count; \ uint64_t cycles_func1, cycles_func2; \ uint64_t start, end; \ - int64_t a = 0, b = 0; \ + int64_t a = 0, b = 0; \ \ /* Measure func1 */ \ start = intrin_timestamp_counter(); \ @@ -283,7 +306,7 @@ double test_measure_func_time_ns(void (*func)(void *), void *para) ++_test_global_assert_count; \ uint64_t cycles_func; \ uint64_t start, end; \ - int64_t para = 0; \ + int64_t para = 0; \ \ /* Measure func */ \ start = intrin_timestamp_counter(); \ @@ -307,7 +330,7 @@ double test_measure_func_time_ns(void (*func)(void *), void *para) ++_test_assert_count; \ ++_test_global_assert_count; \ double time_func1, time_func2; \ - int64_t a = 0, b = 0; \ + int64_t a = 0, b = 0; \ \ /* Measure func1 */ \ time_func1 = test_measure_func_time_ns(func1, (void *)&a); \ @@ -336,7 +359,7 @@ double test_measure_func_time_ns(void (*func)(void *), void *para) ++_test_assert_count; \ ++_test_global_assert_count; \ double time_func; \ - int64_t para = 0; \ + int64_t para = 0; \ \ /* Measure func */ \ time_func = test_measure_func_time_ns(func, (void *)¶); \ diff --git a/tests/stdlib/HashMapTest.cpp b/tests/stdlib/HashMapTest.cpp index 7201bae..26d9eba 100644 --- a/tests/stdlib/HashMapTest.cpp +++ b/tests/stdlib/HashMapTest.cpp @@ -1,7 +1,7 @@ #include "../TestFramework.h" #include "../../stdlib/HashMap.h" -static void test_hash_alloc() { +static void test_hashmap_alloc() { HashMap hm = {}; hashmap_alloc(&hm, 3, sizeof(HashEntryInt32)); @@ -13,7 +13,7 @@ static void test_hash_alloc() { ASSERT_EQUALS(hm.buf.memory, NULL); } -static void test_hash_insert_int32() { +static void test_hashmap_insert_int32() { HashMap hm = {}; hashmap_alloc(&hm, 3, sizeof(HashEntryInt32)); @@ -40,6 +40,34 @@ static void test_hash_insert_int32() { hashmap_free(&hm); } +static void test_hashmap_dump_load() { + RingMemory ring; + ring_alloc(&ring, 10 * MEGABYTE, 64); + + HashMap hm_dump = {}; + hashmap_alloc(&hm_dump, 3, sizeof(HashEntryInt32)); + + hashmap_insert(&hm_dump, "test1", 1); + hashmap_insert(&hm_dump, "test2", 2); + hashmap_insert(&hm_dump, "test3", 3); + + HashMap hm_load = {}; + hashmap_alloc(&hm_load, 3, sizeof(HashEntryInt32)); + + byte* out = ring_get_memory(&ring, 1024 * 1024); + + int64 dump_size = hashmap_dump(&hm_dump, out); + int64 load_size = hashmap_load(&hm_load, out); + ASSERT_EQUALS(dump_size, load_size); + ASSERT_MEMORY_EQUALS(hm_dump.table, hm_load.table, sizeof(uint16) * hm_dump.buf.count); + ASSERT_MEMORY_EQUALS(hm_dump.buf.memory, hm_load.buf.memory, hm_dump.buf.size); + + hashmap_free(&hm_dump); + hashmap_free(&hm_load); + + ring_free(&ring); +} + #ifdef UBER_TEST #ifdef main #undef main @@ -50,8 +78,9 @@ static void test_hash_insert_int32() { int main() { TEST_INIT(25); - RUN_TEST(test_hash_alloc); - RUN_TEST(test_hash_insert_int32); + RUN_TEST(test_hashmap_alloc); + RUN_TEST(test_hashmap_insert_int32); + RUN_TEST(test_hashmap_dump_load); TEST_FINALIZE(); diff --git a/tests/ui/UILayoutTest.cpp b/tests/ui/UILayoutTest.cpp index ad9d03a..1fdd6de 100644 --- a/tests/ui/UILayoutTest.cpp +++ b/tests/ui/UILayoutTest.cpp @@ -4,42 +4,61 @@ #include "../../system/Allocator.h" static void test_layout_from_file_txt() { - RingMemory ui_layout_ring; - ring_alloc(&ui_layout_ring, 10 * MEGABYTE, 64); + RingMemory ring; + ring_alloc(&ring, 10 * MEGABYTE, 64); UILayout layout; layout.data = (byte *) platform_alloc(2 * MEGABYTE); - - layout_from_file_txt(&layout, "./../../GameEditor/assets/themes/default/scene1.layouttxt", &ui_layout_ring); + layout_from_file_txt(&layout, "./../../GameEditor/assets/themes/default/scene1.layouttxt", &ring); UIElement* element = layout_get_element(&layout, "cmd_window"); ASSERT_NOT_EQUALS(element, NULL); platform_free((void **) &layout.data); - ring_free(&ui_layout_ring); + ring_free(&ring); } -static void test_layout_to_data() { -} +static void test_layout_to_from_data() { + RingMemory ring; + ring_alloc(&ring, 10 * MEGABYTE, 64); -static void test_layout_from_data() { + UILayout layout_dump; + layout_dump.data = (byte *) platform_alloc(2 * MEGABYTE); + layout_from_file_txt(&layout_dump, "./../../GameEditor/assets/themes/default/scene1.layouttxt", &ring); + + UILayout layout_load; + layout_load.data = (byte *) platform_alloc(2 * MEGABYTE); + + byte* out = ring_get_memory(&ring, 1024 * 1024); + + int64 dump_size = layout_to_data(&layout_dump, out); + int64 load_size = layout_from_data(out, &layout_load); + ASSERT_EQUALS(dump_size, load_size); + ASSERT_MEMORY_EQUALS(layout_dump.data, layout_load.data, (uint32) (load_size * 0.8)); + + UIElement* element = layout_get_element(&layout_load, "cmd_window"); + ASSERT_NOT_EQUALS(element, NULL); + + platform_free((void **) &layout_load.data); + platform_free((void **) &layout_dump.data); + ring_free(&ring); } static void test_layout_from_theme() { - RingMemory ui_layout_ring; - ring_alloc(&ui_layout_ring, 10 * MEGABYTE, 64); + RingMemory ring; + ring_alloc(&ring, 10 * MEGABYTE, 64); UILayout layout; layout.data = (byte *) platform_alloc(10 * MEGABYTE); - layout_from_file_txt(&layout, "./../../GameEditor/assets/themes/default/scene1.layouttxt", &ui_layout_ring); + layout_from_file_txt(&layout, "./../../GameEditor/assets/themes/default/scene1.layouttxt", &ring); UIThemeStyle theme1; theme1.data = (byte *) platform_alloc(2 * MEGABYTE); - theme_from_file_txt(&theme1, "./../../GameEditor/assets/themes/default/general.themetxt", &ui_layout_ring); + theme_from_file_txt(&theme1, "./../../GameEditor/assets/themes/default/general.themetxt", &ring); UIThemeStyle theme2; theme2.data = (byte *) platform_alloc(2 * MEGABYTE); - theme_from_file_txt(&theme2, "./../../GameEditor/assets/themes/default/scene1.themetxt", &ui_layout_ring); + theme_from_file_txt(&theme2, "./../../GameEditor/assets/themes/default/scene1.themetxt", &ring); Camera camera = {}; camera.viewport_width = 1024; @@ -49,14 +68,16 @@ static void test_layout_from_theme() { UIElement* element = layout_get_element(&layout, "cmd_window"); ASSERT_NOT_EQUALS(element, NULL); - ASSERT_TRUE(element->state > 0); ASSERT_TRUE(element->children_count > 0); - ASSERT_EQUALS(element->type, UI_ELEMENT_TYPE_VIEW_WINDOW); + + UIWindow* default_style = (UIWindow *) layout_get_element_style(&layout, element, UI_STYLE_TYPE_DEFAULT); + ASSERT_EQUALS_WITH_DELTA(default_style->dimension.dimension.width, 1.0f, 0.001f); + ASSERT_EQUALS_WITH_DELTA(default_style->dimension.dimension.height, 0.25f, 0.001f); platform_free((void **) &layout.data); platform_free((void **) &theme1.data); platform_free((void **) &theme2.data); - ring_free(&ui_layout_ring); + ring_free(&ring); } #ifdef UBER_TEST @@ -67,11 +88,10 @@ static void test_layout_from_theme() { #endif int main() { - TEST_INIT(10); + TEST_INIT(100); RUN_TEST(test_layout_from_file_txt); - RUN_TEST(test_layout_to_data); - RUN_TEST(test_layout_from_data); + RUN_TEST(test_layout_to_from_data); RUN_TEST(test_layout_from_theme); TEST_FINALIZE(); diff --git a/tests/ui/UIThemeTest.cpp b/tests/ui/UIThemeTest.cpp index 9bf8936..57bb531 100644 --- a/tests/ui/UIThemeTest.cpp +++ b/tests/ui/UIThemeTest.cpp @@ -3,13 +3,12 @@ #include "../../system/Allocator.h" static void test_theme_from_file_txt() { - RingMemory ui_theme_ring; - ring_alloc(&ui_theme_ring, 10 * MEGABYTE, 64); + RingMemory ring; + ring_alloc(&ring, 10 * MEGABYTE, 64); UIThemeStyle theme; theme.data = (byte *) platform_alloc(2 * MEGABYTE); - - theme_from_file_txt(&theme, "./../../GameEditor/assets/themes/default/scene1.themetxt", &ui_theme_ring); + theme_from_file_txt(&theme, "./../../GameEditor/assets/themes/default/scene1.themetxt", &ring); UIAttributeGroup* group = theme_style_group(&theme, "#cmd_window"); ASSERT_NOT_EQUALS(group, NULL); @@ -20,13 +19,40 @@ static void test_theme_from_file_txt() { ASSERT_EQUALS(attr->datatype, UI_ATTRIBUTE_DATA_TYPE_F32); ASSERT_EQUALS_WITH_DELTA(attr->value_float, 0.0f, 0.001f); - ring_free(&ui_theme_ring); + platform_free((void **) &theme.data); + ring_free(&ring); } -static void test_theme_to_data() { -} +static void test_theme_to_from_data() { + RingMemory ring; + ring_alloc(&ring, 10 * MEGABYTE, 64); -static void test_theme_from_data() { + UIThemeStyle theme_dump; + theme_dump.data = (byte *) platform_alloc(2 * MEGABYTE); + theme_from_file_txt(&theme_dump, "./../../GameEditor/assets/themes/default/scene1.themetxt", &ring); + + UIThemeStyle theme_load; + theme_load.data = (byte *) platform_alloc(2 * MEGABYTE); + + byte* out = ring_get_memory(&ring, 1024 * 1024); + + int64 dump_size = theme_to_data(&theme_dump, out); + int64 load_size = theme_from_data(out, &theme_load); + ASSERT_EQUALS(dump_size, load_size); + ASSERT_MEMORY_EQUALS(theme_dump.data, theme_load.data, (uint32) (load_size * 0.8)); + + UIAttributeGroup* group = theme_style_group(&theme_load, "#cmd_window"); + ASSERT_NOT_EQUALS(group, NULL); + ASSERT_TRUE(group->attribute_count > 0); + + UIAttribute* attr = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_POSITION_X); + ASSERT_NOT_EQUALS(attr, NULL); + ASSERT_EQUALS(attr->datatype, UI_ATTRIBUTE_DATA_TYPE_F32); + ASSERT_EQUALS_WITH_DELTA(attr->value_float, 0.0f, 0.001f); + + platform_free((void **) &theme_load.data); + platform_free((void **) &theme_dump.data); + ring_free(&ring); } #ifdef UBER_TEST @@ -37,11 +63,10 @@ static void test_theme_from_data() { #endif int main() { - TEST_INIT(10); + TEST_INIT(100); RUN_TEST(test_theme_from_file_txt); - RUN_TEST(test_theme_to_data); - RUN_TEST(test_theme_from_data); + RUN_TEST(test_theme_to_from_data); TEST_FINALIZE(); diff --git a/ui/UIButton.h b/ui/UIButton.h index dcf1ec1..a4401a5 100644 --- a/ui/UIButton.h +++ b/ui/UIButton.h @@ -13,7 +13,7 @@ struct UIButtonState { }; struct UIButton { - + UIAttributeDimension dimension; }; void ui_button_state_serialize(const UIButtonState* __restrict state, byte** __restrict pos) diff --git a/ui/UIElement.h b/ui/UIElement.h index a0a2225..5f82cd9 100644 --- a/ui/UIElement.h +++ b/ui/UIElement.h @@ -19,7 +19,13 @@ enum UIElementType : byte { UI_ELEMENT_TYPE_VIEW_PANEL, UI_ELEMENT_TYPE_VIEW_TAB, UI_ELEMENT_TYPE_CURSOR, + + // Uses a callback function for update/rendering UI_ELEMENT_TYPE_CUSTOM, + + // Doesn't do anything is handled completely manual + UI_ELEMENT_TYPE_MANUAL, + UI_ELEMENT_TYPE_SIZE, }; diff --git a/ui/UIElementType.h b/ui/UIElementType.h index 69e60c7..65c86f2 100644 --- a/ui/UIElementType.h +++ b/ui/UIElementType.h @@ -51,6 +51,8 @@ int32 ui_element_type_size(UIElementType e) return sizeof(UICursor); case UI_ELEMENT_TYPE_CUSTOM: return sizeof(UICustom); + case UI_ELEMENT_TYPE_MANUAL: + return sizeof(UICustom); default: { UNREACHABLE(); } @@ -89,6 +91,8 @@ int32 ui_element_state_size(UIElementType e) return sizeof(UICursorState); case UI_ELEMENT_TYPE_CUSTOM: return sizeof(UICustomState); + case UI_ELEMENT_TYPE_MANUAL: + return sizeof(UICustomState); default: { UNREACHABLE(); } @@ -126,8 +130,12 @@ int32 ui_element_type_to_id(const char* str) return UI_ELEMENT_TYPE_CURSOR; } else if (str_compare("custom", str) == 0) { return UI_ELEMENT_TYPE_CUSTOM; + } else if (str_compare("manual", str) == 0) { + return UI_ELEMENT_TYPE_MANUAL; } + ASSERT_SIMPLE(false); + return -1; } diff --git a/ui/UIInput.h b/ui/UIInput.h index ef54257..1418557 100644 --- a/ui/UIInput.h +++ b/ui/UIInput.h @@ -180,12 +180,12 @@ void ui_input_element_populate( // First set all values, which we can set immediately for (uint32 i = 0; i < group->attribute_count; ++i) { - switch (attributes->attribute_id) { + switch (attributes[i].attribute_id) { case UI_ATTRIBUTE_TYPE_POSITION_X: case UI_ATTRIBUTE_TYPE_DIMENSION_WIDTH: case UI_ATTRIBUTE_TYPE_POSITION_Y: case UI_ATTRIBUTE_TYPE_DIMENSION_HEIGHT: { - ui_theme_assign_dimension(&input->dimension, attributes, 6, variables); + ui_theme_assign_dimension(&input->dimension, &attributes[i], 6, variables); } break; } } diff --git a/ui/UILabel.h b/ui/UILabel.h index 3bc31d7..ba16b2c 100644 --- a/ui/UILabel.h +++ b/ui/UILabel.h @@ -51,15 +51,66 @@ void ui_label_element_unserialize(UILabel* __restrict details, const byte** __re } void ui_label_element_populate( + UILayout* layout, const UIAttributeGroup* __restrict group, - UIElement* __restrict element, - UIStyleType style_type, + UILabel* __restrict label, + UIElement* parent, EvaluatorVariable* __restrict variables ) { - (void *) group; - (void *) element; - (void) style_type; - (void *) variables; + if (parent) { + // @bug How to ensure that the parent is initialized before the child element + // Currently the order of the initialization depends on the theme file, NOT the layout file + // We could fix it by loading the style based on the layout order but this would result in many misses when looking up styles + // The reason for these misses are, that often only 1-2 style_types exist per element + + v4_f32* parent_dimension; + switch (parent->type) { + case UI_ELEMENT_TYPE_VIEW_WINDOW: { + UIWindow* parent_window = (UIWindow *) (layout->data + parent->style_types[UI_STYLE_TYPE_ACTIVE]); + parent_dimension = &parent_window->dimension.dimension; + } break; + case UI_ELEMENT_TYPE_VIEW_PANEL: { + UIPanel* parent_window = (UIPanel *) (layout->data + parent->style_types[UI_STYLE_TYPE_ACTIVE]); + parent_dimension = &parent_window->dimension.dimension; + } break; + case UI_ELEMENT_TYPE_BUTTON: { + UIButton* parent_button = (UIButton *) (layout->data + parent->style_types[UI_STYLE_TYPE_ACTIVE]); + parent_dimension = &parent_button->dimension.dimension; + } break; + case UI_ELEMENT_TYPE_INPUT: { + UIInput* parent_input = (UIInput *) (layout->data + parent->style_types[UI_STYLE_TYPE_ACTIVE]); + parent_dimension = &parent_input->dimension.dimension; + } break; + default: + UNREACHABLE(); + } + + variables[2].value = parent_dimension->x; + variables[3].value = parent_dimension->y; + variables[4].value = parent_dimension->width; + variables[5].value = parent_dimension->height; + } + + UIAttribute* attributes = (UIAttribute *) (group + 1); + + // First set all values, which we can set immediately + for (uint32 i = 0; i < group->attribute_count; ++i) { + switch (attributes[i].attribute_id) { + case UI_ATTRIBUTE_TYPE_POSITION_X: + case UI_ATTRIBUTE_TYPE_DIMENSION_WIDTH: + case UI_ATTRIBUTE_TYPE_POSITION_Y: + case UI_ATTRIBUTE_TYPE_DIMENSION_HEIGHT: { + ui_theme_assign_dimension(&label->dimension, &attributes[i], 6, variables); + } break; + case UI_ATTRIBUTE_TYPE_FONT_NAME: + case UI_ATTRIBUTE_TYPE_FONT_COLOR: + case UI_ATTRIBUTE_TYPE_FONT_SIZE: + case UI_ATTRIBUTE_TYPE_FONT_WEIGHT: + case UI_ATTRIBUTE_TYPE_FONT_LINE_HEIGHT: { + ui_theme_assign_font(&label->font, &attributes[i]); + } break; + } + } } int32 ui_label_element_update(UILayout* layout, UIElement* element) diff --git a/ui/UILayout.cpp b/ui/UILayout.cpp index d26908c..d7e3b83 100644 --- a/ui/UILayout.cpp +++ b/ui/UILayout.cpp @@ -15,6 +15,7 @@ #include "UIElementType.h" #include "UIInput.h" #include "UILabel.h" +#include "UIWindow.h" // @todo We should add some asserts that ensure that the respective structs at least start at a 4byte memory alignment @@ -39,11 +40,14 @@ void ui_layout_count_direct_children(UIElement* __restrict element, const char* str_move_past(&pos, '\n'); continue; - } else if (level <= parent_level || !str_is_alphanum(*pos)) { + } else if (level <= parent_level) { // We are no longer inside of element str_move_past(&pos, '\n'); break; + } else if (is_eol(pos)) { + pos += is_eol(pos); + continue; } str_move_past(&pos, '\n'); @@ -127,6 +131,9 @@ void layout_from_file_txt( while (*pos != '\0') { // Skip all white spaces str_skip_empty(&pos); + if (*pos == '\0') { + break; + } ++temp_element_count; @@ -153,7 +160,7 @@ void layout_from_file_txt( // Create root element UIElement* root = (UIElement *) element_data; - hashmap_insert(&layout->hash_map, ":root", (int32) (element_data - layout->data)); + hashmap_insert(&layout->hash_map, "root", (int32) (element_data - layout->data)); ui_layout_count_direct_children(root, pos, -4); // NOTE: The root element cannot have any animations or vertices @@ -288,55 +295,55 @@ static void ui_layout_serialize_element( HashEntryInt32* entry, byte* data, - byte** pos + byte** out ) { // @performance Are we sure the data is nicely aligned? // Probably depends on the from_txt function and the start of layout->data UIElement* element = (UIElement *) (data + entry->value); - **pos = element->state_flag; - *pos += sizeof(element->state_flag); + **out = element->state_flag; + *out += sizeof(element->state_flag); - **pos = element->type; - *pos += sizeof(element->type); + **out = element->type; + *out += sizeof(element->type); - **pos = element->style_old; - *pos += sizeof(element->style_old); + **out = element->style_old; + *out += sizeof(element->style_old); - **pos = element->style_new; - *pos += sizeof(element->style_new); + **out = element->style_new; + *out += sizeof(element->style_new); - *((uint32 *) *pos) = SWAP_ENDIAN_LITTLE(element->parent); - *pos += sizeof(element->parent); + *((uint32 *) *out) = SWAP_ENDIAN_LITTLE(element->parent); + *out += sizeof(element->parent); - *((uint32 *) *pos) = SWAP_ENDIAN_LITTLE(element->state); - *pos += sizeof(element->state); + *((uint32 *) *out) = SWAP_ENDIAN_LITTLE(element->state); + *out += sizeof(element->state); // Details for (int32 i = 0; i < UI_STYLE_TYPE_SIZE; ++i) { - *((uint32 *) *pos) = SWAP_ENDIAN_LITTLE(element->style_types[i]); - *pos += sizeof(element->style_types[i]); + *((uint32 *) *out) = SWAP_ENDIAN_LITTLE(element->style_types[i]); + *out += sizeof(element->style_types[i]); } - *((uint16 *) *pos) = SWAP_ENDIAN_LITTLE(element->children_count); - *pos += sizeof(element->children_count); + *((uint16 *) *out) = SWAP_ENDIAN_LITTLE(element->children_count); + *out += sizeof(element->children_count); /* We don't save the animation state since that is always 0 in the file - memset(*pos, 0, sizeof(element->animation_state)); - *pos += sizeof(element->animation_state); + memset(*out, 0, sizeof(element->animation_state)); + *out += sizeof(element->animation_state); */ - *((uint16 *) *pos) = SWAP_ENDIAN_LITTLE(element->animation_count); - *pos += sizeof(element->animation_count); + *((uint16 *) *out) = SWAP_ENDIAN_LITTLE(element->animation_count); + *out += sizeof(element->animation_count); - *((uint32 *) *pos) = SWAP_ENDIAN_LITTLE(element->animations); - *pos += sizeof(element->animations); + *((uint32 *) *out) = SWAP_ENDIAN_LITTLE(element->animations); + *out += sizeof(element->animations); - *((uint16 *) *pos) = SWAP_ENDIAN_LITTLE(element->vertex_count); - *pos += sizeof(element->vertex_count); + *((uint16 *) *out) = SWAP_ENDIAN_LITTLE(element->vertex_count); + *out += sizeof(element->vertex_count); - *((uint32 *) *pos) = SWAP_ENDIAN_LITTLE(element->vertices_active); - *pos += sizeof(element->vertices_active); + *((uint32 *) *out) = SWAP_ENDIAN_LITTLE(element->vertices_active); + *out += sizeof(element->vertices_active); // Output dynamic length content directly after UIElement // @@ -347,19 +354,19 @@ void ui_layout_serialize_element( // At this point we may have this data available now (if we save a cached version = layout+theme) // Obviously, this won't have an effect on the current run-tim but would make the memory layout nicer on the next load // It would be kind of a self-optimizing ui layout system :). - // Of course, updating the reference values (uint32) will be challenging since the file pos will still not be the same as the offset due to alignment and padding + // Of course, updating the reference values (uint32) will be challenging since the file out will still not be the same as the offset due to alignment and padding // We would probably need a helper_offset value that gets passed around also as parameter of this function ////////////////////////////////////// // Children array uint32* children = (uint32 *) (element + 1); for (int32 i = 0; i < element->children_count; ++i) { - *((uint32 *) *pos) = SWAP_ENDIAN_LITTLE(children[i]); - *pos += sizeof(*children); + *((uint32 *) *out) = SWAP_ENDIAN_LITTLE(children[i]); + *out += sizeof(*children); } // State element data e.g. UIInputState - ui_layout_serialize_element_state(element->type, data + element->state, pos); + ui_layout_serialize_element_state(element->type, data + element->state, out); // detailed element data/style_types e.g. UIInput // When you create a layout this is should only contain the default style type @@ -370,32 +377,32 @@ void ui_layout_serialize_element( continue; } - ui_layout_serialize_element_detail(element->type, data + element->style_types[i], pos); + ui_layout_serialize_element_detail(element->type, data + element->style_types[i], out); } UIAnimation* animations = (UIAnimation *) (data + element->animations); int32 element_style_type_size = ui_element_type_size(element->type); for (int32 i = 0; i < element->animation_count; ++i) { - **pos = animations[i].style_old; - *pos += sizeof(animations[i].style_old); + **out = animations[i].style_old; + *out += sizeof(animations[i].style_old); - **pos = animations[i].style_new; - *pos += sizeof(animations[i].style_new); + **out = animations[i].style_new; + *out += sizeof(animations[i].style_new); - *((uint16 *) *pos) = SWAP_ENDIAN_LITTLE(animations[i].duration); - *pos += sizeof(animations[i].duration); + *((uint16 *) *out) = SWAP_ENDIAN_LITTLE(animations[i].duration); + *out += sizeof(animations[i].duration); - **pos = animations[i].anim_type; - *pos += sizeof(animations[i].anim_type); + **out = animations[i].anim_type; + *out += sizeof(animations[i].anim_type); - **pos = animations[i].keyframe_count; - *pos += sizeof(animations[i].keyframe_count); + **out = animations[i].keyframe_count; + *out += sizeof(animations[i].keyframe_count); // The keyframes are the element detail information (e.g. UIInput) and they are located after the respective Animation definition byte* keyframes = (byte *) (&animations[i] + 1); for (int32 j = 0; j < animations[i].keyframe_count; ++j) { - ui_layout_serialize_element_detail(element->type, keyframes + j * element_style_type_size, pos); + ui_layout_serialize_element_detail(element->type, keyframes + j * element_style_type_size, out); } } } @@ -404,30 +411,23 @@ int32 layout_to_data( const UILayout* __restrict layout, byte* __restrict data ) { - byte* pos = data; - byte* max_pos = data; + byte* out = data; // version - *((int32 *) pos) = SWAP_ENDIAN_LITTLE(UI_LAYOUT_VERSION); - pos += sizeof(int32); + *((int32 *) out) = SWAP_ENDIAN_LITTLE(UI_LAYOUT_VERSION); + out += sizeof(int32); // hashmap - byte* start = pos; - pos += hashmap_dump(&layout->hash_map, pos); + out += hashmap_dump(&layout->hash_map, out); // UIElement data uint32 chunk_id = 0; chunk_iterate_start(&layout->hash_map.buf, chunk_id) HashEntryInt32* entry = (HashEntryInt32 *) chunk_get_element((ChunkMemory *) &layout->hash_map.buf, chunk_id); - - pos = start + entry->value; - ui_layout_serialize_element(entry, layout->data, &pos); - if (pos > max_pos) { - max_pos = pos; - } + ui_layout_serialize_element(entry, layout->data, &out); chunk_iterate_end; - return (int32) (max_pos - data); + return (int32) (out - data); } static @@ -449,54 +449,53 @@ void ui_layout_unserialize_element_detail(UIElementType type, void* __restrict d } static -void ui_layout_parse_element(HashEntryInt32* entry, byte* data, const byte** pos) +void ui_layout_parse_element(HashEntryInt32* entry, byte* data, const byte** in) { // @performance Are we sure the data is nicely aligned? // Probably depends on the from_txt function and the start of layout->data UIElement* element = (UIElement *) (data + entry->value); - element->state_flag = **pos; - *pos += sizeof(element->state_flag); + element->state_flag = **in; + *in += sizeof(element->state_flag); - element->type = (UIElementType) **pos; - *pos += sizeof(element->type); + element->type = (UIElementType) **in; + *in += sizeof(element->type); - element->style_old = (UIStyleType) **pos; - *pos += sizeof(element->style_old); + element->style_old = (UIStyleType) **in; + *in += sizeof(element->style_old); - element->style_new = (UIStyleType) **pos; - *pos += sizeof(element->style_new); + element->style_new = (UIStyleType) **in; + *in += sizeof(element->style_new); - element->parent = SWAP_ENDIAN_LITTLE(*((uint32 *) *pos)); - *pos += sizeof(element->parent); + element->parent = SWAP_ENDIAN_LITTLE(*((uint32 *) *in)); + *in += sizeof(element->parent); - element->state = SWAP_ENDIAN_LITTLE(*((uint32 *) *pos)); - *pos += sizeof(element->state); + element->state = SWAP_ENDIAN_LITTLE(*((uint32 *) *in)); + *in += sizeof(element->state); // Details for (int32 i = 0; i < UI_STYLE_TYPE_SIZE; ++i) { - element->style_types[i] = SWAP_ENDIAN_LITTLE(*((uint32 *) *pos)); - *pos += sizeof(element->style_types[i]); + element->style_types[i] = SWAP_ENDIAN_LITTLE(*((uint32 *) *in)); + *in += sizeof(element->style_types[i]); } - element->children_count = SWAP_ENDIAN_LITTLE(*((uint16 *) *pos)); - *pos += sizeof(element->children_count); + element->children_count = SWAP_ENDIAN_LITTLE(*((uint16 *) *in)); + *in += sizeof(element->children_count); // @question Do we really have to do that? Shouldn't the animation_state data be 0 anyways or could there be garbage values? memset(&element->animation_state, 0, sizeof(element->animation_state)); - element->animation_count = SWAP_ENDIAN_LITTLE(*((uint16 *) *pos)); - *pos += sizeof(element->animation_count); + element->animation_count = SWAP_ENDIAN_LITTLE(*((uint16 *) *in)); + *in += sizeof(element->animation_count); - element->animations = SWAP_ENDIAN_LITTLE(*((uint32 *) *pos)); - *pos += sizeof(element->animations); + element->animations = SWAP_ENDIAN_LITTLE(*((uint32 *) *in)); + *in += sizeof(element->animations); - element->vertex_count = SWAP_ENDIAN_LITTLE(*((uint16 *) *pos)); - *pos += sizeof(element->vertex_count); + element->vertex_count = SWAP_ENDIAN_LITTLE(*((uint16 *) *in)); + *in += sizeof(element->vertex_count); - // @bug this needs to be changed? - element->vertices_active = SWAP_ENDIAN_LITTLE(*((uint32 *) *pos)); - *pos += sizeof(element->vertices_active); + element->vertices_active = SWAP_ENDIAN_LITTLE(*((uint32 *) *in)); + *in += sizeof(element->vertices_active); // Load dynamic length content // Some of the content belongs directly after the element but some of it belongs at very specific offsets @@ -512,12 +511,12 @@ void ui_layout_parse_element(HashEntryInt32* entry, byte* data, const byte** pos // Children array uint32* children = (uint32 *) (element + 1); for (int32 i = 0; i < element->children_count; ++i) { - children[i] = SWAP_ENDIAN_LITTLE(*((uint32 *) *pos)); - *pos += sizeof(*children); + children[i] = SWAP_ENDIAN_LITTLE(*((uint32 *) *in)); + *in += sizeof(*children); } // State element data e.g. UIInputState - ui_layout_unserialize_element_state(element->type, data + element->state, pos); + ui_layout_unserialize_element_state(element->type, data + element->state, in); // detailed element data/style_types e.g. UIInput for (int32 i = 0; i < UI_STYLE_TYPE_SIZE; ++i) { @@ -525,32 +524,32 @@ void ui_layout_parse_element(HashEntryInt32* entry, byte* data, const byte** pos continue; } - ui_layout_unserialize_element_detail(element->type, data + element->style_types[i], pos); + ui_layout_unserialize_element_detail(element->type, data + element->style_types[i], in); } UIAnimation* animations = (UIAnimation *) (data + element->animations); int32 element_style_type_size = ui_element_type_size(element->type); for (int32 i = 0; i < element->animation_count; ++i) { - animations[i].style_old = (UIStyleType) **pos; - *pos += sizeof(animations[i].style_old); + animations[i].style_old = (UIStyleType) **in; + *in += sizeof(animations[i].style_old); - animations[i].style_new = (UIStyleType) **pos; - *pos += sizeof(animations[i].style_new); + animations[i].style_new = (UIStyleType) **in; + *in += sizeof(animations[i].style_new); - animations[i].duration = SWAP_ENDIAN_LITTLE(*((uint16 *) *pos)); - *pos += sizeof(animations[i].duration); + animations[i].duration = SWAP_ENDIAN_LITTLE(*((uint16 *) *in)); + *in += sizeof(animations[i].duration); - animations[i].anim_type = (AnimationEaseType) **pos; - *pos += sizeof(animations[i].anim_type); + animations[i].anim_type = (AnimationEaseType) **in; + *in += sizeof(animations[i].anim_type); - animations[i].keyframe_count = **pos; - *pos += sizeof(animations[i].keyframe_count); + animations[i].keyframe_count = **in; + *in += sizeof(animations[i].keyframe_count); // The keyframes are the element detail information (e.g. UIInput) and they are located after the respective Animation definition byte* keyframes = (byte *) (&animations[i] + 1); for (int32 j = 0; j < animations[i].keyframe_count; ++j) { - ui_layout_unserialize_element_detail(element->type, keyframes + j * element_style_type_size, pos); + ui_layout_unserialize_element_detail(element->type, keyframes + j * element_style_type_size, in); } } } @@ -561,33 +560,26 @@ int32 layout_from_data( const byte* __restrict data, UILayout* __restrict layout ) { - const byte* pos = data; - const byte* max_pos = pos; + const byte* in = data; - int32 version = *((int32 *) pos); - pos += sizeof(version); + int32 version = SWAP_ENDIAN_LITTLE(*((int32 *) in)); + in += sizeof(version); // Prepare hashmap (incl. reserve memory) by initializing it the same way we originally did // Of course we still need to populate the data using hashmap_load() - hashmap_create(&layout->hash_map, (int32) SWAP_ENDIAN_LITTLE(*((uint32 *) pos)), sizeof(HashEntryInt32), layout->data); + hashmap_create(&layout->hash_map, (int32) SWAP_ENDIAN_LITTLE(*((uint32 *) in)), sizeof(HashEntryInt32), layout->data); - const byte* start = data; - pos += hashmap_load(&layout->hash_map, pos); + in += hashmap_load(&layout->hash_map, in); // layout data // @performance We are iterating the hashmap twice (hashmap_load and here) uint32 chunk_id = 0; chunk_iterate_start(&layout->hash_map.buf, chunk_id) HashEntryInt32* entry = (HashEntryInt32 *) chunk_get_element((ChunkMemory *) &layout->hash_map.buf, chunk_id); - - pos = start + entry->value; - ui_layout_parse_element(entry, layout->data, &pos); - if (pos > max_pos) { - max_pos = pos; - } + ui_layout_parse_element(entry, layout->data, &in); chunk_iterate_end; - layout->layout_size = (uint32) (max_pos - data); + layout->layout_size = (uint32) (in - data); return (int32) layout->layout_size; } @@ -630,7 +622,8 @@ void layout_from_theme( continue; } - HashEntryInt32* entry = (HashEntryInt32 *) hashmap_get_entry(&layout->hash_map, style_entry->key); + // +1 to skip '#' or '.' + HashEntryInt32* entry = (HashEntryInt32 *) hashmap_get_entry(&layout->hash_map, style_entry->key + 1); if (!entry) { // Couldn't find the base element continue; @@ -643,6 +636,16 @@ void layout_from_theme( // @todo Continue implementation switch (element->type) { + case UI_ELEMENT_TYPE_LABEL: { + ui_label_state_populate(group, (UILabelState *) (layout->data + element->state)); + ui_label_element_populate( + layout, + group, + (UILabel *) (layout->data + element->style_types[UI_STYLE_TYPE_DEFAULT]), + parent, + variables + ); + } break; case UI_ELEMENT_TYPE_INPUT: { ui_input_state_populate(group, (UIInputState *) (layout->data + element->state)); ui_input_element_populate( @@ -653,6 +656,16 @@ void layout_from_theme( variables ); } break; + case UI_ELEMENT_TYPE_VIEW_WINDOW: { + ui_window_state_populate(group, (UIWindowState *) (layout->data + element->state)); + ui_window_element_populate( + layout, + group, + (UIWindow *) (layout->data + element->style_types[UI_STYLE_TYPE_DEFAULT]), + parent, + variables + ); + } break; } chunk_iterate_end; @@ -674,7 +687,8 @@ void layout_from_theme( } char pure_name[HASH_MAP_MAX_KEY_LENGTH]; - str_copy_until(style_entry->key, pure_name, ':'); + // +1 to skip '#' or '.' + str_copy_until(style_entry->key + 1, pure_name, ':'); HashEntryInt32* entry = (HashEntryInt32 *) hashmap_get_entry(&layout->hash_map, pure_name); if (!entry) { @@ -704,6 +718,15 @@ void layout_from_theme( // @todo Continue implementation switch (element->type) { + case UI_ELEMENT_TYPE_LABEL: { + ui_label_element_populate( + layout, + group, + (UILabel *) (layout->data + element->style_types[style_type]), + parent, + variables + ); + } break; case UI_ELEMENT_TYPE_INPUT: { ui_input_element_populate( layout, @@ -713,6 +736,15 @@ void layout_from_theme( variables ); } break; + case UI_ELEMENT_TYPE_VIEW_WINDOW: { + ui_window_element_populate( + layout, + group, + (UIWindow *) (layout->data + element->style_types[style_type]), + parent, + variables + ); + } break; } chunk_iterate_end; } @@ -769,7 +801,21 @@ void ui_layout_update(UILayout* layout, UIElement* element) { } } +// @question We might want to change the names of update/render +// In some cases it's more like cache/render +// @question We might want to allow rendering without caching (currently we always rely on the cache) +// This increases our RAM requirements (every vertex is in cache AND in the asset AND in VRAM) +// However, this also has the benefit of allowing us to ONLY re-render individual elements + +// @performance In our immediate mode solution we decided the update/render based on a bitfield +// That is very efficient, the code below isn't doing that maybe there is a way to implement that here as well? +// I don't think so but it would be nice +// This function caches the vertices void ui_layout_update_dfs(UILayout* layout, UIElement* element, byte category = 0) { + if (element->type == UI_ELEMENT_TYPE_MANUAL) { + return; + } + if (element->category == category) { ui_layout_update(layout, element); } @@ -785,16 +831,21 @@ uint32 ui_layout_render_dfs( UIElement* element, Vertex3DTextureColor* __restrict vertices, byte category = 0 ) { + if (element->type == UI_ELEMENT_TYPE_MANUAL) { + return 0; + } + uint32 vertex_count = 0; - if (element->category == category) { + if (element->vertex_count && element->category == category) { memcpy(vertices, layout->vertices_active + element->vertices_active, sizeof(*vertices) * element->vertex_count); vertices += element->vertex_count; vertex_count += element->vertex_count; } + uint32* children = (uint32 *) (element + 1); for (int32 i = 0; i < element->children_count; ++i) { - uint32 child_vertex_count = ui_layout_render_dfs(layout, element, vertices, category); + uint32 child_vertex_count = ui_layout_render_dfs(layout, (UIElement *) (layout->data + children[i]), vertices, category); vertices += child_vertex_count; vertex_count += child_vertex_count; } @@ -807,6 +858,10 @@ uint32 ui_layout_update_render_dfs( UIElement* __restrict element, Vertex3DTextureColor* __restrict vertices, byte category = 0 ) { + if (element->type == UI_ELEMENT_TYPE_MANUAL) { + return 0; + } + uint32 vertex_count = 0; if (element->category == category) { @@ -817,8 +872,9 @@ uint32 ui_layout_update_render_dfs( vertex_count += element->vertex_count; } + uint32* children = (uint32 *) (element + 1); for (int32 i = 0; i < element->children_count; ++i) { - uint32 child_vertex_count = ui_layout_update_render_dfs(layout, element, vertices, category); + uint32 child_vertex_count = ui_layout_update_render_dfs(layout, (UIElement *) (layout->data + children[i]), vertices, category); vertices += child_vertex_count; vertex_count += child_vertex_count; } diff --git a/ui/UITheme.h b/ui/UITheme.h index b5c2a92..de81859 100644 --- a/ui/UITheme.h +++ b/ui/UITheme.h @@ -204,46 +204,46 @@ void theme_from_file_txt( } static inline -void ui_theme_parse_group(HashEntryInt32* entry, byte* data, const byte** pos) +void ui_theme_parse_group(HashEntryInt32* entry, byte* data, const byte** in) { // @performance Are we sure the data is nicely aligned? // Probably depends on the from_txt function and the start of theme->data UIAttributeGroup* group = (UIAttributeGroup *) (data + entry->value); - group->attribute_count = SWAP_ENDIAN_LITTLE(*((uint32 *) *pos)); - *pos += sizeof(group->attribute_count); + group->attribute_count = SWAP_ENDIAN_LITTLE(*((uint32 *) *in)); + *in += sizeof(group->attribute_count); UIAttribute* attribute_reference = (UIAttribute *) (group + 1); for (uint32 j = 0; j < group->attribute_count; ++j) { - attribute_reference[j].attribute_id = (UIAttributeType) SWAP_ENDIAN_LITTLE(*((uint16 *) *pos)); - *pos += sizeof(attribute_reference[j].attribute_id); + attribute_reference[j].attribute_id = (UIAttributeType) SWAP_ENDIAN_LITTLE(*((uint16 *) *in)); + *in += sizeof(attribute_reference[j].attribute_id); - attribute_reference[j].datatype = *((UIAttributeDataType *) *pos); - *pos += sizeof(attribute_reference[j].datatype); + attribute_reference[j].datatype = *((UIAttributeDataType *) *in); + *in += sizeof(attribute_reference[j].datatype); if (attribute_reference[j].datatype == UI_ATTRIBUTE_DATA_TYPE_INT) { - attribute_reference[j].value_int = SWAP_ENDIAN_LITTLE(*((int32 *) *pos)); - *pos += sizeof(attribute_reference[j].value_int); + attribute_reference[j].value_int = SWAP_ENDIAN_LITTLE(*((int32 *) *in)); + *in += sizeof(attribute_reference[j].value_int); } else if (attribute_reference[j].datatype == UI_ATTRIBUTE_DATA_TYPE_INT) { - attribute_reference[j].value_float = SWAP_ENDIAN_LITTLE(*((f32 *) *pos)); - *pos += sizeof(attribute_reference[j].value_float); + attribute_reference[j].value_float = SWAP_ENDIAN_LITTLE(*((f32 *) *in)); + *in += sizeof(attribute_reference[j].value_float); } else if (attribute_reference[j].datatype == UI_ATTRIBUTE_DATA_TYPE_STR) { - memcpy(attribute_reference[j].value_str, *pos, sizeof(attribute_reference[j].value_str)); - *pos += sizeof(attribute_reference[j].value_str); + memcpy(attribute_reference[j].value_str, *in, sizeof(attribute_reference[j].value_str)); + *in += sizeof(attribute_reference[j].value_str); } else if (attribute_reference[j].datatype == UI_ATTRIBUTE_DATA_TYPE_V4_F32) { - attribute_reference[j].value_v4_f32.x = SWAP_ENDIAN_LITTLE(*((f32 *) *pos)); - *pos += sizeof(attribute_reference[j].value_v4_f32.x); + attribute_reference[j].value_v4_f32.x = SWAP_ENDIAN_LITTLE(*((f32 *) *in)); + *in += sizeof(attribute_reference[j].value_v4_f32.x); - attribute_reference[j].value_v4_f32.y = SWAP_ENDIAN_LITTLE(*((f32 *) *pos)); - *pos += sizeof(attribute_reference[j].value_v4_f32.y); + attribute_reference[j].value_v4_f32.y = SWAP_ENDIAN_LITTLE(*((f32 *) *in)); + *in += sizeof(attribute_reference[j].value_v4_f32.y); - attribute_reference[j].value_v4_f32.z = SWAP_ENDIAN_LITTLE(*((f32 *) *pos)); - *pos += sizeof(attribute_reference[j].value_v4_f32.z); + attribute_reference[j].value_v4_f32.z = SWAP_ENDIAN_LITTLE(*((f32 *) *in)); + *in += sizeof(attribute_reference[j].value_v4_f32.z); - attribute_reference[j].value_v4_f32.w = SWAP_ENDIAN_LITTLE(*((f32 *) *pos)); - *pos += sizeof(attribute_reference[j].value_v4_f32.w); + attribute_reference[j].value_v4_f32.w = SWAP_ENDIAN_LITTLE(*((f32 *) *in)); + *in += sizeof(attribute_reference[j].value_v4_f32.w); } } } @@ -267,19 +267,17 @@ int32 theme_from_data( const byte* __restrict data, UIThemeStyle* __restrict theme ) { - const byte* pos = data; - const byte* max_pos = data; + const byte* in = data; - int32 version = *((int32 *) pos); - pos += sizeof(version); + int32 version = SWAP_ENDIAN_LITTLE(*((int32 *) in)); + in += sizeof(version); // Prepare hashmap (incl. reserve memory) by initializing it the same way we originally did // Of course we still need to populate the data using hashmap_load() // The value is a int64 (because this is the value of the chunk buffer size but the hashmap only allows int32) - hashmap_create(&theme->hash_map, (int32) SWAP_ENDIAN_LITTLE(*((uint32 *) pos)), sizeof(HashEntryInt32), theme->data); + hashmap_create(&theme->hash_map, (int32) SWAP_ENDIAN_LITTLE(*((uint32 *) in)), sizeof(HashEntryInt32), theme->data); - const byte* start = theme->hash_map.buf.memory; - pos += hashmap_load(&theme->hash_map, pos); + in += hashmap_load(&theme->hash_map, in); // theme data // Layout: first load the size of the group, then load the individual attributes @@ -287,16 +285,10 @@ int32 theme_from_data( uint32 chunk_id = 0; chunk_iterate_start(&theme->hash_map.buf, chunk_id) HashEntryInt32* entry = (HashEntryInt32 *) chunk_get_element((ChunkMemory *) &theme->hash_map.buf, chunk_id); - - // This way we now could access the data directly without the silly theme->data + entry->value calc. - pos = start + entry->value; - ui_theme_parse_group(entry, theme->data, &pos); - if (pos > max_pos) { - max_pos = pos; - } + ui_theme_parse_group(entry, theme->data, &in); chunk_iterate_end; - return (int32) (max_pos - data); + return (int32) (in - data); } // Calculates the maximum theme size @@ -311,45 +303,45 @@ int64 theme_data_size_max(const UIThemeStyle* theme) // @todo Why do even need **pos, shouldn't it just be *pos static inline -void ui_theme_serialize_group(const HashEntryInt32* entry, byte* data, byte** pos) +void ui_theme_serialize_group(const HashEntryInt32* entry, byte* data, byte** out) { // @performance Are we sure the data is nicely aligned? // Probably depends on the from_txt function and the start of theme->data UIAttributeGroup* group = (UIAttributeGroup *) (data + entry->value); - *((uint32 *) *pos) = SWAP_ENDIAN_LITTLE(group->attribute_count); - *pos += sizeof(group->attribute_count); + *((uint32 *) *out) = SWAP_ENDIAN_LITTLE(group->attribute_count); + *out += sizeof(group->attribute_count); UIAttribute* attribute_reference = (UIAttribute *) (group + 1); for (uint32 j = 0; j < group->attribute_count; ++j) { - *((uint16 *) *pos) = SWAP_ENDIAN_LITTLE(attribute_reference[j].attribute_id); - *pos += sizeof(attribute_reference[j].attribute_id); + *((uint16 *) *out) = SWAP_ENDIAN_LITTLE(attribute_reference[j].attribute_id); + *out += sizeof(attribute_reference[j].attribute_id); - *((byte *) *pos) = attribute_reference[j].datatype; - *pos += sizeof(attribute_reference[j].datatype); + *((byte *) *out) = attribute_reference[j].datatype; + *out += sizeof(attribute_reference[j].datatype); if (attribute_reference[j].datatype == UI_ATTRIBUTE_DATA_TYPE_INT) { - *((int32 *) *pos) = SWAP_ENDIAN_LITTLE(attribute_reference[j].value_int); - *pos += sizeof(attribute_reference[j].value_int); + *((int32 *) *out) = SWAP_ENDIAN_LITTLE(attribute_reference[j].value_int); + *out += sizeof(attribute_reference[j].value_int); } else if (attribute_reference[j].datatype == UI_ATTRIBUTE_DATA_TYPE_INT) { - *((f32 *) *pos) = SWAP_ENDIAN_LITTLE(attribute_reference[j].value_float); - *pos += sizeof(attribute_reference[j].value_float); + *((f32 *) *out) = SWAP_ENDIAN_LITTLE(attribute_reference[j].value_float); + *out += sizeof(attribute_reference[j].value_float); } else if (attribute_reference[j].datatype == UI_ATTRIBUTE_DATA_TYPE_STR) { - memcpy(*pos, attribute_reference[j].value_str, sizeof(attribute_reference[j].value_str)); - *pos += sizeof(attribute_reference[j].value_str); + memcpy(*out, attribute_reference[j].value_str, sizeof(attribute_reference[j].value_str)); + *out += sizeof(attribute_reference[j].value_str); } else if (attribute_reference[j].datatype == UI_ATTRIBUTE_DATA_TYPE_V4_F32) { - *((f32 *) *pos) = SWAP_ENDIAN_LITTLE(attribute_reference[j].value_v4_f32.x); - *pos += sizeof(attribute_reference[j].value_v4_f32.x); + *((f32 *) *out) = SWAP_ENDIAN_LITTLE(attribute_reference[j].value_v4_f32.x); + *out += sizeof(attribute_reference[j].value_v4_f32.x); - *((f32 *) *pos) = SWAP_ENDIAN_LITTLE(attribute_reference[j].value_v4_f32.y); - *pos += sizeof(attribute_reference[j].value_v4_f32.y); + *((f32 *) *out) = SWAP_ENDIAN_LITTLE(attribute_reference[j].value_v4_f32.y); + *out += sizeof(attribute_reference[j].value_v4_f32.y); - *((f32 *) *pos) = SWAP_ENDIAN_LITTLE(attribute_reference[j].value_v4_f32.z); - *pos += sizeof(attribute_reference[j].value_v4_f32.z); + *((f32 *) *out) = SWAP_ENDIAN_LITTLE(attribute_reference[j].value_v4_f32.z); + *out += sizeof(attribute_reference[j].value_v4_f32.z); - *((f32 *) *pos) = SWAP_ENDIAN_LITTLE(attribute_reference[j].value_v4_f32.w); - *pos += sizeof(attribute_reference[j].value_v4_f32.w); + *((f32 *) *out) = SWAP_ENDIAN_LITTLE(attribute_reference[j].value_v4_f32.w); + *out += sizeof(attribute_reference[j].value_v4_f32.w); } } } @@ -373,31 +365,24 @@ int32 theme_to_data( const UIThemeStyle* __restrict theme, byte* __restrict data ) { - byte* pos = data; - byte* max_pos = data; + byte* out = data; // version - *((int32 *) pos) = SWAP_ENDIAN_LITTLE(UI_THEME_VERSION); - pos += sizeof(int32); + *((int32 *) out) = SWAP_ENDIAN_LITTLE(UI_THEME_VERSION); + out += sizeof(int32); // hashmap - byte* start = pos; - pos += hashmap_dump(&theme->hash_map, pos); + out += hashmap_dump(&theme->hash_map, out); // theme data // Layout: first save the size of the group, then save the individual attributes uint32 chunk_id = 0; chunk_iterate_start(&theme->hash_map.buf, chunk_id) const HashEntryInt32* entry = (HashEntryInt32 *) chunk_get_element((ChunkMemory *) &theme->hash_map.buf, chunk_id); - - pos = start + entry->value; - ui_theme_serialize_group(entry, theme->data, &pos); - if (pos > max_pos) { - max_pos = pos; - } + ui_theme_serialize_group(entry, theme->data, &out); chunk_iterate_end; - return (int32) (max_pos - data); + return (int32) (out - data); } #endif \ No newline at end of file diff --git a/ui/UIWindow.h b/ui/UIWindow.h index 1feb77b..1209c4a 100644 --- a/ui/UIWindow.h +++ b/ui/UIWindow.h @@ -8,6 +8,7 @@ #include "attribute/UIAttributeBackground.h" #include "attribute/UIAttributeDimension.h" #include "UIAnimation.h" +#include "UIPanel.h" #include "UIStyleType.h" struct UIWindowState { @@ -29,4 +30,78 @@ struct UIWindow { UIWindow* styles[UI_STYLE_TYPE_SIZE]; }; +void ui_window_state_serialize(const UIWindowState* __restrict state, byte** __restrict pos) +{ + (void *) state; + (void **) pos; +} + +void ui_window_state_unserialize(UIWindowState* __restrict state, const byte** __restrict pos) +{ + (void *) state; + (void **) pos; +} + +void ui_window_state_populate(const UIAttributeGroup* __restrict group, UIWindowState* __restrict state) +{ + (void *) group; + (void *) state; +} + +void ui_window_element_serialize(const UIWindow* __restrict details, byte** __restrict pos) +{ + (void *) details; + (void **) pos; +} + +void ui_window_element_unserialize(UIWindow* __restrict details, const byte** __restrict pos) +{ + (void *) details; + (void **) pos; +} + +void ui_window_element_populate( + UILayout* layout, + const UIAttributeGroup* __restrict group, + UIWindow* __restrict window, + UIElement* parent, + EvaluatorVariable* __restrict variables +) { + if (parent) { + // @bug How to ensure that the parent is initialized before the child element + // Currently the order of the initialization depends on the theme file, NOT the layout file + // We could fix it by loading the style based on the layout order but this would result in many misses when looking up styles + // The reason for these misses are, that often only 1-2 style_types exist per element + + v4_f32* parent_dimension; + switch (parent->type) { + case UI_ELEMENT_TYPE_VIEW_PANEL: { + UIPanel* parent_window = (UIPanel *) (layout->data + parent->style_types[UI_STYLE_TYPE_ACTIVE]); + parent_dimension = &parent_window->dimension.dimension; + } break; + default: + UNREACHABLE(); + } + + variables[2].value = parent_dimension->x; + variables[3].value = parent_dimension->y; + variables[4].value = parent_dimension->width; + variables[5].value = parent_dimension->height; + } + + UIAttribute* attributes = (UIAttribute *) (group + 1); + + // First set all values, which we can set immediately + for (uint32 i = 0; i < group->attribute_count; ++i) { + switch (attributes[i].attribute_id) { + case UI_ATTRIBUTE_TYPE_POSITION_X: + case UI_ATTRIBUTE_TYPE_DIMENSION_WIDTH: + case UI_ATTRIBUTE_TYPE_POSITION_Y: + case UI_ATTRIBUTE_TYPE_DIMENSION_HEIGHT: { + ui_theme_assign_dimension(&window->dimension, &attributes[i], 6, variables); + } break; + } + } +} + #endif \ No newline at end of file diff --git a/ui/attribute/UIAttribute.h b/ui/attribute/UIAttribute.h index b0907b7..a28c25f 100644 --- a/ui/attribute/UIAttribute.h +++ b/ui/attribute/UIAttribute.h @@ -7,6 +7,7 @@ #include "../../compiler/CompilerUtils.h" #include "UIAttributeType.h" #include "UIAttributeDimension.h" +#include "UIAttributeFont.h" enum UIAttributeDataType : byte { UI_ATTRIBUTE_DATA_TYPE_INT, @@ -24,6 +25,7 @@ struct UIAttribute { // @performance The string makes this struct really large when it is not needed in 95% of the cases char value_str[32]; int32 value_int; + uint32 value_uint; f32 value_float; v4_f32 value_v4_f32; }; @@ -218,19 +220,21 @@ void ui_attribute_parse_value(UIAttribute* attr, const char* attribute_name, con hexstr_to_rgba(&attr->value_v4_f32, pos); attr->datatype = UI_ATTRIBUTE_DATA_TYPE_V4_F32; } else { - str_copy_until(attr->value_str, value, "\r\n"); + str_copy_until(value, attr->value_str, "\r\n"); attr->datatype = UI_ATTRIBUTE_DATA_TYPE_STR; } } inline -void ui_theme_assign_f32(f32* a, const UIAttribute* attr, int32 variable_count, const EvaluatorVariable* variables) +void ui_theme_assign_f32(f32* a, const UIAttribute* attr, int32 variable_count = 0, const EvaluatorVariable* variables = NULL) { if (attr->datatype == UI_ATTRIBUTE_DATA_TYPE_INT) { *a = (f32) attr->value_int; } else if (attr->datatype == UI_ATTRIBUTE_DATA_TYPE_F32) { *a = (f32) attr->value_float; } else if (attr->datatype == UI_ATTRIBUTE_DATA_TYPE_STR) { + ASSERT_SIMPLE(str_length(attr->value_str) > 0); + char value[32]; memcpy(value, attr->value_str, ARRAY_COUNT(attr->value_str)); *a = (f32) evaluator_evaluate(value, variable_count, variables); @@ -259,4 +263,29 @@ void ui_theme_assign_dimension(UIAttributeDimension* dimension, const UIAttribut } } +inline +void ui_theme_assign_font(UIAttributeFont* font, const UIAttribute* attr) +{ + switch (attr->attribute_id) { + case UI_ATTRIBUTE_TYPE_FONT_NAME: { + UNREACHABLE(); + } break; + case UI_ATTRIBUTE_TYPE_FONT_COLOR: { + font->color = attr->value_uint; + } break; + case UI_ATTRIBUTE_TYPE_FONT_SIZE: { + font->size = attr->value_float; + } break; + case UI_ATTRIBUTE_TYPE_FONT_WEIGHT: { + font->weight = attr->value_float; + } break; + case UI_ATTRIBUTE_TYPE_FONT_LINE_HEIGHT: { + font->line_height = attr->value_float; + } break; + default: { + UNREACHABLE(); + } + } +} + #endif \ No newline at end of file diff --git a/ui/attribute/UIAttributeFont.h b/ui/attribute/UIAttributeFont.h index 0132cdb..9180ca8 100644 --- a/ui/attribute/UIAttributeFont.h +++ b/ui/attribute/UIAttributeFont.h @@ -2,6 +2,7 @@ #define TOS_UI_ATTRIBUTE_FONT_H #include "../../stdlib/Types.h" +#include "UIAttributeShadow.h" enum UIFontDecoration : byte { UI_FONT_DECORATION_UNDERLINE = 1 << 0, @@ -10,6 +11,7 @@ enum UIFontDecoration : byte { struct UIAttributeFont { f32 size; + f32 line_height; uint32 color; f32 weight; UIAttributeShadow shadow_outer; diff --git a/ui/attribute/UIAttributeType.h b/ui/attribute/UIAttributeType.h index 2fb0bce..94e2f9e 100644 --- a/ui/attribute/UIAttributeType.h +++ b/ui/attribute/UIAttributeType.h @@ -31,7 +31,6 @@ enum UIAttributeType : uint16 { UI_ATTRIBUTE_TYPE_CONTENT_ALIGN_V, UI_ATTRIBUTE_TYPE_FONT_NAME, - UI_ATTRIBUTE_TYPE_FONT_COLOR_INDEX, UI_ATTRIBUTE_TYPE_FONT_COLOR, UI_ATTRIBUTE_TYPE_FONT_SIZE, UI_ATTRIBUTE_TYPE_FONT_WEIGHT, diff --git a/utils/StringUtils.h b/utils/StringUtils.h index 42d6f7f..647d2f4 100644 --- a/utils/StringUtils.h +++ b/utils/StringUtils.h @@ -1353,7 +1353,7 @@ void hexstr_to_rgba(v4_f32* rgba, const char* hex) } inline constexpr -void str_pad(const char* input, char* output, char pad, size_t len) { +void str_pad_right(const char* input, char* output, char pad, size_t len) { size_t i = 0; for (; i < len && input[i] != '\0'; ++i) { output[i] = input[i]; @@ -1364,6 +1364,20 @@ void str_pad(const char* input, char* output, char pad, size_t len) { } } +inline constexpr +void str_pad_left(const char* input, char* output, char pad, size_t len) { + size_t input_len = str_length(input); + + size_t i = 0; + for (; i < len - input_len; ++i) { + *output++ = pad; + } + + for (; i < len && input[i] != '\0'; ++i) { + output[i] = input[i]; + } +} + inline f32 str_to_float(const char* str, const char** pos = NULL) {