townengine/src/twn_util.c
2025-03-10 07:05:45 +03:00

457 lines
12 KiB
C

#include "twn_util.h"
#include "twn_util_c.h"
#include "twn_engine_context_c.h"
#include <SDL2/SDL.h>
#include <physfsrwops.h>
#include <stb_ds.h>
#include <stdarg.h>
static struct ProfileItem {
char const *key;
struct Profile {
uint64_t tick_start;
uint64_t tick_accum;
uint64_t sample_count;
uint64_t worst_tick;
bool active;
} value;
} *profiles;
void cry_impl(const char *file, const int line, const char *title, const char *text) {
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION,
"TEARS AT %s:%d: %s ... %s", file, line, title, text);
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title, text, NULL);
}
static void log_impl(const char *restrict format, va_list args, SDL_LogPriority priority) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION,
priority,
format,
args);
#pragma GCC diagnostic pop
}
void log_info(const char *restrict format, ...) {
va_list args;
va_start(args, format);
log_impl(format, args, SDL_LOG_PRIORITY_INFO);
va_end(args);
}
void log_critical(const char *restrict format, ...) {
va_list args;
va_start(args, format);
log_impl(format, args, SDL_LOG_PRIORITY_CRITICAL);
va_end(args);
}
void log_warn(const char *restrict format, ...) {
va_list args;
va_start(args, format);
log_impl(format, args, SDL_LOG_PRIORITY_WARN);
va_end(args);
}
_Noreturn static void alloc_failure_death(void) {
log_critical("Allocation failure. Aborting NOW.");
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
"MEMORY ALLOCATION FAILURE",
"CRITICAL MEMORY ALLOCATION FAILED. "
"EXITING IMMEDIATELY!",
NULL);
die_abruptly();
}
_Noreturn void die_abruptly(void) {
/* a zombie window will linger if we don't at least try to quit SDL */
SDL_Quit();
abort();
}
void *cmalloc(size_t size) {
void *ptr = SDL_malloc(size);
if (ptr == NULL)
alloc_failure_death();
return ptr;
}
void *crealloc(void *ptr, size_t size) {
void *out = SDL_realloc(ptr, size);
if (out == NULL)
alloc_failure_death();
return out;
}
void *ccalloc(size_t num, size_t size) {
void *ptr = SDL_calloc(num, size);
if (ptr == NULL)
alloc_failure_death();
return ptr;
}
int64_t file_to_bytes(const char *path, unsigned char **buf_out) {
SDL_RWops *handle = PHYSFSRWOPS_openRead(path);
if (handle == NULL) {
return -1;
}
int64_t data_size = SDL_RWseek(handle, 0, RW_SEEK_END);
SDL_RWseek(handle, 0, RW_SEEK_SET); /* reset offset into data */
*buf_out = cmalloc(data_size);
SDL_RWread(handle, *buf_out, sizeof **buf_out, data_size / sizeof **buf_out);
SDL_RWclose(handle); /* we got all we needed from the stream */
return data_size;
}
char *file_to_str(const char *path, size_t *out_len) {
SDL_RWops *handle = PHYSFSRWOPS_openRead(path);
if (handle == NULL) {
return NULL;
}
int64_t data_size = SDL_RWseek(handle, 0, RW_SEEK_END);
SDL_RWseek(handle, 0, RW_SEEK_SET); /* reset offset into data */
char *str_out = cmalloc(data_size + 1); /* data plus final null */
size_t len = data_size / sizeof *str_out;
SDL_RWread(handle, str_out, sizeof *str_out, len);
SDL_RWclose(handle); /* we got all we needed from the stream */
str_out[len] = '\0';
if (out_len)
*out_len = len;
return str_out;
}
struct ImageCollectionData {
char *list;
size_t length;
};
static PHYSFS_EnumerateCallbackResult image_collection_callback(void *data,
const char *origdir,
const char *fname)
{
struct ImageCollectionData *result = data;
(void)origdir;
/* TODO: settle down on supported images */
size_t file_len = SDL_strlen(fname);
size_t dir_len = SDL_strlen(origdir);
if (CHECK_ENDING(fname, file_len, ".png")) {
result->list = SDL_realloc(result->list, result->length + dir_len + file_len + 1);
if (result->length != 0)
result->list[result->length-1] = '\n';
SDL_strlcpy(&result->list[result->length], origdir, dir_len);
result->length += dir_len;
result->list[result->length-1] = '/';
SDL_strlcpy(&result->list[result->length], fname, file_len + 1);
result->length += file_len + 1;
result->list[result->length-1] = '\0';
}
return PHYSFS_ENUM_OK;
}
/* TODO: caching on file and operation inputs */
static String *read_files;
String file_read(char const *file, char const *operation) {
if (!file) {
log_warn("No file specified");
return (String){NULL, 0};
}
if (!operation) {
log_warn("No operation specified");
return (String){NULL, 0};
}
if (SDL_strncmp(operation, ":string", sizeof (":string") - 1) == 0) {
size_t length;
char *data = file_to_str(file, &length);
if (!data)
return (String){NULL, 0};
String s = {data, (float)length};
arrpush(read_files, s);
return s;
} else if (SDL_strncmp(operation, ":binary", sizeof (":binary") - 1) == 0) {
uint8_t *data;
int64_t length = file_to_bytes(file, &data);
if (length == -1)
return (String){NULL, 0};
String s = {(char *)data, (float)length};
arrpush(read_files, s);
return s;
} else if (SDL_strncmp(operation, ":exists", sizeof (":exists") - 1) == 0) {
bool exists = file_exists(file);
return exists ? (String){"yes", 3} : (String){"no", 2};
} else if (SDL_strncmp(operation, ":images", sizeof (":images") - 1) == 0) {
struct ImageCollectionData list = {0};
if (PHYSFS_enumerate(file, image_collection_callback, &list) == 0) {
CRY_PHYSFS("Error enumerating images");
if (list.list) SDL_free(list.list);
return (String){NULL, 0};
}
if (list.list == NULL)
return (String){NULL, 0};
String s = {(char *)list.list, (float)list.length - 1};
arrpush(read_files, s);
return s;
} else
log_warn("No valid operation specified by %s", operation);
return (String){NULL, 0};
}
void file_read_garbage_collect(void) {
for (size_t i = 0; i < arrlenu(read_files); ++i) {
SDL_assert_always(read_files[i].data && read_files[i].length >= 0);
SDL_free(read_files[i].data);
}
arrfree(read_files);
read_files = NULL;
}
bool file_exists(const char *path) {
return PHYSFS_exists(path);
}
void textures_dump_atlases(void) {
PHYSFS_mkdir("/dump");
size_t i = 0;
for (; i < arrlenu(ctx.texture_cache.atlas_surfaces); ++i) {
char *buf = NULL;
SDL_asprintf(&buf, "/dump/atlas%zd.bmp", (ssize_t)i);
SDL_RWops *handle = PHYSFSRWOPS_openWrite(buf);
if (handle == NULL) {
CRY("Texture atlas dump failed.", "File could not be opened");
return;
}
SDL_SaveBMP_RW(ctx.texture_cache.atlas_surfaces[i], handle, true);
log_info("Dumped atlas %zu to %s", i, buf);
SDL_free(buf);
}
}
/* TODO: have our own */
Rect rect_overlap(const Rect a, const Rect b) {
SDL_FRect a_sdl = { a.x, a.y, a.w, a.h };
SDL_FRect b_sdl = { b.x, b.y, b.w, b.h };
SDL_FRect result_sdl = { 0 };
(void)SDL_IntersectFRect(&a_sdl, &b_sdl, &result_sdl);
return (Rect){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h };
}
/* TODO: have our own */
bool rect_intersects(const Rect a, const Rect b) {
SDL_FRect a_sdl = { a.x, a.y, a.w, a.h };
SDL_FRect b_sdl = { b.x, b.y, b.w, b.h };
return SDL_HasIntersectionF(&a_sdl, &b_sdl);
}
float timer_tick_seconds(float seconds_left) {
SDL_assert(seconds_left >= 0);
return MAX(seconds_left - ((float)ctx.delta_time / (float)ctx.clocks_per_second), 0.0f);
}
TimerElapseSecondsResult timer_elapse_seconds(float seconds_left, float interval) {
SDL_assert(seconds_left >= 0);
SDL_assert(interval > 0);
seconds_left -= (float)ctx.delta_time / (float)ctx.clocks_per_second;
bool elapsed = false;
if (seconds_left <= 0.0f) {
elapsed = true;
seconds_left += interval;
}
return (TimerElapseSecondsResult) {
.elapsed = elapsed,
.seconds_left = seconds_left,
.interval = interval,
};
}
/* TODO: handle utf8 */
char *expand_asterisk(const char *mask, const char *to) {
const char *offset = SDL_strchr(mask, '*');
if (!offset) {
CRY("Invalid path", "Asterisk should be given.");
return NULL;
}
size_t const mask_len = SDL_strlen(mask);
size_t const to_len = SDL_strlen(to);
char *str = SDL_malloc(mask_len + to_len); /* NULL included, replacing the original asterisk */
SDL_memcpy(str, mask, offset - mask);
SDL_memcpy(str + (offset - mask), to, to_len);
/* NULL byte is copied from here */
SDL_memcpy(str + (offset - mask) + to_len, offset + 1, mask_len - (offset - mask));
return str;
}
void profile_start(char const *profile) {
/* stamp time immediately, so to not have influence of our profile lookup */
uint64_t const counter = SDL_GetPerformanceCounter();
struct ProfileItem *p = shgetp_null(profiles, profile);
if (p) {
p->value.tick_start = counter;
p->value.active = true;
} else {
shput(profiles, profile, ((struct Profile) {
.tick_start = counter,
.active = true,
}));
}
}
void profile_end(char const *profile) {
/* stamp time immediately, so to not have influence of our profile lookup */
uint64_t const counter = SDL_GetPerformanceCounter();
struct ProfileItem *p = shgetp_null(profiles, profile);
if (!p || !p->value.active) {
log_warn("Profile %s wasn't started!", profile);
return;
}
uint64_t const took = counter - p->value.tick_start;
p->value.tick_accum += took;
p->value.sample_count++;
p->value.active = false;
if (p->value.worst_tick < took)
p->value.worst_tick = took;
}
static char const *format_profile_time(double ticks) {
static char strings[2][128];
static char *current = strings[0];
double const seconds = (double)ticks / (double)(SDL_GetPerformanceFrequency());
/* display in seconds */
if (seconds >= 0.1)
SDL_snprintf(current, 128, "%fs", seconds);
/* display in milliseconds */
else
SDL_snprintf(current, 128, "%fms", seconds * 1000);
const char *const result = current;
current += 128;
if (current >= strings[1]) current = strings[0];
return result;
}
void profile_list_stats(void) {
for (size_t i = 0; i < shlenu(profiles); ++i) {
if (profiles[i].value.sample_count == 0) {
log_warn("Profile %s was started, but not once finished.", profiles[i].key);
}
else if (profiles[i].value.sample_count == 1) {
log_info("Profile '%s' took: %s",
profiles[i].key,
format_profile_time((double)profiles[i].value.tick_accum));
}
else if (profiles[i].value.sample_count > 1) {
log_info("Profile '%s' on average took: %s, worst case: %s, sample count: %llu",
profiles[i].key,
format_profile_time((double)profiles[i].value.tick_accum / (double)profiles[i].value.sample_count),
format_profile_time((double)profiles[i].value.worst_tick),
profiles[i].value.sample_count);
}
}
}
void log_string(char const *value, char const *message) {
if (!value) return;
if (!message)
log_info("%s", value);
else
log_info("%s: %s", message, value);
}
void log_float(float value, char const *message) {
if (!message) message = "float";
log_info("%s = %f", message, (double)value);
}
void log_vec2(Vec2 vector, char const *message) {
if (!message) message = "Vec2";
log_info("%s = (%f, %f)", message, (double)vector.x, (double)vector.y);
}
void log_vec3(Vec3 vector, char const *message) {
if (!message) message = "Vec3";
log_info("%s = (%f, %f, %f)", message, (double)vector.x, (double)vector.y, (double)vector.z);
}
void log_rect(Rect rect, char const *message) {
if (!message) message = "Rect";
log_info("%s = (%f, %f, %f, %f)", message, (double)rect.x, (double)rect.y, (double)rect.w, (double)rect.h);
}