#include "twn_util.h" #include "twn_util_c.h" #include "twn_engine_context_c.h" #include #include #include #include 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); }