398 lines
10 KiB
C
398 lines
10 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;
|
|
}
|
|
|
|
|
|
double clamp(double d, double min, double max) {
|
|
const double t = d < min ? min : d;
|
|
return t > max ? max : t;
|
|
}
|
|
|
|
|
|
float clampf(float f, float min, float max) {
|
|
const float t = f < min ? min : f;
|
|
return t > max ? max : t;
|
|
}
|
|
|
|
|
|
int clampi(int i, int min, int max) {
|
|
const int t = i < min ? min : i;
|
|
return t > max ? max : t;
|
|
}
|
|
|
|
|
|
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) {
|
|
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';
|
|
|
|
return str_out;
|
|
}
|
|
|
|
|
|
bool file_exists(const char *path) {
|
|
return PHYSFS_exists(path);
|
|
}
|
|
|
|
|
|
void textures_dump_atlases(void) {
|
|
PHYSFS_mkdir("/dump");
|
|
|
|
/* TODO: png instead of bmp */
|
|
const char string_template[] = "/dump/atlas%zd.bmp";
|
|
|
|
size_t i = 0;
|
|
for (; i < arrlenu(ctx.texture_cache.atlas_surfaces); ++i) {
|
|
char *buf = NULL;
|
|
SDL_asprintf(&buf, string_template, 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);
|
|
}
|
|
}
|
|
|
|
|
|
bool strends(const char *str, const char *suffix) {
|
|
size_t str_length = SDL_strlen(str);
|
|
size_t suffix_length = SDL_strlen(suffix);
|
|
|
|
if (suffix_length > str_length)
|
|
return false;
|
|
|
|
return SDL_memcmp((str + str_length) - suffix_length, suffix, suffix_length) == 0;
|
|
}
|
|
|
|
|
|
/* 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);
|
|
}
|
|
|
|
|
|
Vec2 rect_center(Rect rect) {
|
|
return (Vec2){
|
|
.x = rect.x + rect.w / 2,
|
|
.y = rect.y + rect.h / 2,
|
|
};
|
|
}
|
|
|
|
|
|
int32_t timer_tick_frames(int32_t frames_left) {
|
|
SDL_assert(frames_left >= 0);
|
|
return MAX(frames_left - 1, 0);
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
TimerElapseFramesResult timer_elapse_frames(int32_t frames_left, int32_t interval) {
|
|
SDL_assert(frames_left >= 0);
|
|
SDL_assert(interval > 0);
|
|
|
|
frames_left -= 1;
|
|
bool elapsed = false;
|
|
if (frames_left <= 0) {
|
|
elapsed = true;
|
|
frames_left += interval;
|
|
}
|
|
return (TimerElapseFramesResult) {
|
|
.elapsed = elapsed,
|
|
.frames_left = frames_left
|
|
};
|
|
}
|
|
|
|
|
|
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
|
|
};
|
|
}
|
|
|
|
|
|
/* 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 profile[const static 1]) {
|
|
/* 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 profile[const static 1]) {
|
|
/* 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_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);
|
|
}
|