getting closer to our first rendering with the UILayout, app working.

This commit is contained in:
Dennis Eichhorn 2025-02-12 03:23:18 +01:00
parent a8191a239a
commit c7b881e6b7
41 changed files with 1507 additions and 1076 deletions

View File

@ -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; })

View File

@ -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() {

View File

@ -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) {

View File

@ -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

View File

@ -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
////////////////////////////////////

View File

@ -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);

View File

@ -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 <windows.h>
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

View File

@ -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 <windows.h>
#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

34
log/DebugContainer.h Normal file
View File

@ -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

View File

@ -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__)

226
log/Log.h
View File

@ -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

150
log/PerformanceProfiler.h Normal file
View File

@ -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

64
log/Stats.h Normal file
View File

@ -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

View File

@ -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 <intrin.h>
#else
#include <x86intrin.h>
#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

View File

@ -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;

View File

@ -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"

View File

@ -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;

View File

@ -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"

View File

@ -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

View File

@ -13,7 +13,7 @@
#include <dbghelp.h>
#include <stdio.h>
#include <stdlib.h>
#include "../../log/Debug.cpp"
#include "../../log/Log.h"
#ifdef _MSC_VER
#pragma comment(lib, "dbghelp.lib")

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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": [

View File

@ -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 *)&para); \

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -13,7 +13,7 @@ struct UIButtonState {
};
struct UIButton {
UIAttributeDimension dimension;
};
void ui_button_state_serialize(const UIButtonState* __restrict state, byte** __restrict pos)

View File

@ -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,
};

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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)

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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,

View File

@ -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)
{