Wed, 02 Jul 2025 23:21:17 +0200
resolve TODOs regarding input.h
a) mouse position must be integer, because it can be negative (though rarely)
b) we should not trade "access complexity" for space in the scancodes array
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * Copyright 2023 Mike Becker. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "ascension/context.h" #include "ascension/error.h" #include <SDL2/SDL.h> #include <SDL2/SDL_ttf.h> #include <SDL2/SDL_image.h> #include <GL/glew.h> #include <time.h> AscContext asc_context; static uint64_t asc_nanos(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return 1000000000ull*(uint64_t)ts.tv_sec + (uint64_t)ts.tv_nsec; } // forward declare non-public font cache functions void asc_font_cache_init(void); void asc_font_cache_destroy(void); void asc_context_initialize(void) { if (asc_test_flag(asc_context.flags, ASC_FLAG_INITILIZED)) return; memset(&asc_context, 0, sizeof(AscContext)); // initialize default paths asc_context.font_path = cx_strdup(CX_STR("./fonts")); asc_context.shader_path = cx_strdup(CX_STR("./shader")); asc_context.texture_path = cx_strdup(CX_STR("./textures")); // initialize the font cache asc_font_cache_init(); // set a default font but do not load it asc_context.active_font.style = ASC_FONT_REGULAR; asc_context.active_font.size = 14; // no window, yet asc_context.active_window = ASC_MAX_WINDOWS; // initialize error buffer cxBufferInit( &asc_context.error_buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND ); // initialize SDL const int supported_img_flags = IMG_INIT_PNG | IMG_INIT_JPG; if (SDL_Init(SDL_INIT_VIDEO) < 0) { asc_error("Failed to initialize SDL: %s", SDL_GetError()); } else if (TTF_Init() < 0) { asc_error("Failed to initialize SDL_ttf: %s", TTF_GetError()); } else if (IMG_Init(supported_img_flags) != supported_img_flags) { asc_error("Failed to initialize SDL_img: %s", IMG_GetError()); } SDL_ClearError(); asc_context.total_nanos = asc_nanos(); asc_set_flag(asc_context.flags, ASC_FLAG_INITILIZED); asc_dprintf("Ascension context initialized."); } void asc_context_destroy(void) { // destroy windows for (unsigned int i = 0 ; i < ASC_MAX_WINDOWS ; i++) { asc_window_destroy(i); } // destroy the font cache asc_font_cache_destroy(); // quit SDL IMG_Quit(); TTF_Quit(); SDL_Quit(); // destroy the error buffer cxBufferDestroy(&asc_context.error_buffer); asc_context.flags = 0; asc_dprintf("Ascension context destroyed."); // destroy the path information cx_strfree(&asc_context.font_path); cx_strfree(&asc_context.shader_path); cx_strfree(&asc_context.texture_path); } void asc_context_quit(void) { asc_set_flag(asc_context.flags, ASC_FLAG_QUIT); } AscWindow *asc_active_window_assert() { if (asc_context.active_window < ASC_MAX_WINDOWS) { return &asc_context.windows[asc_context.active_window]; } else { asc_dprintf("Window access attempted without active window"); abort(); } } static void asc_event_window_resized(Uint32 id, Sint32 width, Sint32 height) { unsigned int i = asc_window_index(id); if (i < ASC_MAX_WINDOWS) { asc_context.windows[i].resized = true; asc_context.windows[i].dimensions.width = (unsigned) width; asc_context.windows[i].dimensions.height = (unsigned) height; } } bool asc_loop_next(void) { // reset mouse motion asc_context.input.mouse_xrel = 0; asc_context.input.mouse_yrel = 0; // reset key flags for (unsigned int i = 0 ; i < SDL_NUM_SCANCODES ; i++) { asc_clear_flag(asc_context.input.keys[i], ASC_KEY_RELEASE_FLAG|ASC_KEY_PRESS_FLAG); } // dispatch SDL events SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: asc_set_flag(asc_context.flags, ASC_FLAG_QUIT); break; case SDL_WINDOWEVENT: { if (event.window.event == SDL_WINDOWEVENT_RESIZED) { asc_event_window_resized( event.window.windowID, event.window.data1, event.window.data2 ); } else if (event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) { unsigned int idx = asc_window_index(event.window.windowID); asc_context.windows[idx].focused = true; } else if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) { unsigned int idx = asc_window_index(event.window.windowID); asc_context.windows[idx].focused = false; } break; } case SDL_MOUSEMOTION: { // accumulate relative motion asc_context.input.mouse_xrel += event.motion.xrel; asc_context.input.mouse_yrel += event.motion.yrel; // update absolute position asc_context.input.mouse_x = event.motion.x; asc_context.input.mouse_y = event.motion.y; // update which window the mouse was seen in asc_context.input.mouse_window = asc_window_index(event.motion.windowID); break; } case SDL_KEYDOWN: // we only set the down and press flags if the key is not already known to be down if (asc_key_up(event.key.keysym.scancode)) { asc_context.input.keys[event.key.keysym.scancode] = ASC_KEY_DOWN_FLAG|ASC_KEY_PRESS_FLAG; } break; case SDL_KEYUP: // we can directly set the release flag - it will be cleared next round asc_context.input.keys[event.key.keysym.scancode] = ASC_KEY_RELEASE_FLAG; break; } } // compute frame time // TODO: think about whether frame rate is actually a per-window thing // the answer is hopefully NO, because that would mean behaviors are also a per-window thing uint64_t frame_nanos, ns; do { ns = asc_nanos(); frame_nanos = ns - asc_context.total_nanos; } while (frame_nanos == 0); asc_context.frame_nanos = frame_nanos; unsigned long long fps_1k = asc_seconds(1000) / frame_nanos; asc_context.frame_rate = (float)fps_1k / 1000.f; if (asc_context.frame_rate < 5) { // effectively stop the world when the frame rate drops too low asc_context.frame_factor = 0; } else { asc_context.frame_factor = 1.f / asc_context.frame_rate; } asc_context.total_nanos = ns; // sync the windows for (unsigned int i = 0 ; i < ASC_MAX_WINDOWS ; i++) { asc_window_sync(i); } return !asc_test_flag(asc_context.flags, ASC_FLAG_QUIT); } void asc_set_font_path(const char *path) { cx_strcpy(&asc_context.font_path, cx_str(path)); } void asc_set_shader_path(const char *path) { cx_strcpy(&asc_context.shader_path, cx_str(path)); } void asc_set_texture_path(const char *path) { cx_strcpy(&asc_context.texture_path, cx_str(path)); }