consistently refer to windows by ID - fixes #381

Thu, 18 Apr 2024 22:53:55 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 18 Apr 2024 22:53:55 +0200
changeset 65
9c44c55d327a
parent 64
f18dc427f86f
child 66
8297afa1c29c

consistently refer to windows by ID - fixes #381

This change discovered that the font cache is completely broken. We created issue #387 for this.

src/ascension/context.h file | annotate | diff | comparison | revisions
src/ascension/input.h file | annotate | diff | comparison | revisions
src/ascension/ui.h file | annotate | diff | comparison | revisions
src/ascension/ui/font.h file | annotate | diff | comparison | revisions
src/ascension/ui/text.h file | annotate | diff | comparison | revisions
src/ascension/window.h file | annotate | diff | comparison | revisions
src/context.c file | annotate | diff | comparison | revisions
src/font.c file | annotate | diff | comparison | revisions
src/primitives.c file | annotate | diff | comparison | revisions
src/scene.c file | annotate | diff | comparison | revisions
src/text.c file | annotate | diff | comparison | revisions
src/window.c file | annotate | diff | comparison | revisions
test/snake.c file | annotate | diff | comparison | revisions
--- a/src/ascension/context.h	Thu Apr 18 21:53:53 2024 +0200
+++ b/src/ascension/context.h	Thu Apr 18 22:53:55 2024 +0200
@@ -52,10 +52,11 @@
     CxBuffer error_buffer;
     AscInput input;
     AscWindow windows[ASC_MAX_WINDOWS];
-    AscWindow *active_window;
+    // TODO: rework how fonts are cached
     AscFont fonts[ASC_MAX_FONTS];
-    unsigned int fonts_loaded;
-    AscFont const *active_font;
+    unsigned char fonts_loaded;
+    unsigned char active_window;
+    unsigned char active_font;
     asc_col4i ink;
     uint64_t frame_nanos;
     uint64_t total_nanos;
@@ -64,6 +65,29 @@
 /** Global ascension context. */
 extern AscContext asc_context;
 
+/**
+ * The currently active font.
+ * @see asc_font()
+ */
+#define asc_active_font \
+    (&asc_context.fonts[asc_context.active_font])
+
+/**
+ * The currently active window in the context.
+ * @see asc_window_activate()
+ */
+#define asc_active_window \
+    (&asc_context.windows[asc_context.active_window])
+
+static inline bool asc_assert_active_window() {
+    if (asc_context.active_window < ASC_MAX_WINDOWS) {
+        return true;
+    } else {
+        asc_error("A graphics operation was performed without active window");
+        return false;
+    }
+}
+
 void asc_context_initialize(void);
 void asc_context_destroy(void);
 
@@ -86,10 +110,5 @@
 #define asc_ink_rgba(r,g,b,a) asc_context.ink = (asc_col4i){(r),(g),(b),(a)}
 #define asc_ink_rgb(r,g,b) asc_context.ink = (asc_col4i){(r),(g),(b),255u}
 
-/**
- * Sets the active drawing font.
- */
-#define asc_set_font(font) asc_context.active_font = (font)
-
 #endif /* ASCENSION_CONTEXT_H */
 
--- a/src/ascension/input.h	Thu Apr 18 21:53:53 2024 +0200
+++ b/src/ascension/input.h	Thu Apr 18 22:53:55 2024 +0200
@@ -45,4 +45,12 @@
 #define asc_key_pressed(scancode) \
     (asc_context.input.keys[SDL_SCANCODE_##scancode])
 
+#define asc_mouse_x asc_context.input.mouse_x
+#define asc_mouse_y asc_context.input.mouse_y
+#define asc_mouse_pos (asc_vec2i) {asc_mouse_x, asc_mouse_y}
+
+#define asc_mouse_move_x asc_context.input.mouse_xrel
+#define asc_mouse_move_y asc_context.input.mouse_yrel
+#define asc_mouse_move (asc_vec2i) {asc_mouse_move_x, asc_mouse_move_y}
+
 #endif //ASCENSION_INPUT_H
--- a/src/ascension/ui.h	Thu Apr 18 21:53:53 2024 +0200
+++ b/src/ascension/ui.h	Thu Apr 18 22:53:55 2024 +0200
@@ -31,7 +31,7 @@
 #include "ui/text.h"
 
 #define asc_add_ui_node(node) \
-    asc_scene_node_link(asc_window_active->ui, node)
+    asc_scene_node_link(asc_active_window->ui, node)
 
 #endif /* ASCENSION_UI_H */
 
--- a/src/ascension/ui/font.h	Thu Apr 18 21:53:53 2024 +0200
+++ b/src/ascension/ui/font.h	Thu Apr 18 22:53:55 2024 +0200
@@ -52,43 +52,25 @@
      */
     int size;
     /**
+     * TODO: remove from public struct
      * Pointer to the SDL TTF font structure.
      */
     TTF_Font *ptr;
 } AscFont;
 
 /**
- * Loads a font with the given style and size.
+ * Activates a font with the given style and size.
  *
- * The font is cached and returned faster the next time you call this
+ * The font is cached and activated faster the next time you call this
  * function with the same arguments. However, when you reach the maximum
  * number of fonts, the cache is completely cleared and rebuilt.
  *
  * That means in general, that you should not be using too many fonts of
- * different sizes at the same time, and you should not keep the pointer
- * returned by this function too long, because you would risking cache misses.
+ * different sizes at the same time.
  *
  * @param style the style
  * @param size the point size
- * @return a pointer to the font structure (do not free)
  */
-AscFont const *asc_font(enum AscFontStyle style, int size);
-
-/**
- * Unloads all cached fonts from the context.
- */
-void asc_font_cache_clear(void);
-
-/**
- * Checks, if the font is still loaded and reloads it, if required.
- *
- * There is no need to call this function manually. Every Ascension function
- * that you pass a font will do that for you and use the returned pointer in
- * case of a cache miss.
- *
- * @param font the font to validate
- * @return \p font, or a pointer to the new location of the loaded font
- */
-AscFont const *asc_font_cache_validate(AscFont const *font);
+void asc_font(enum AscFontStyle style, int size);
 
 #endif //ASCENSION_FONT_H
--- a/src/ascension/ui/text.h	Thu Apr 18 21:53:53 2024 +0200
+++ b/src/ascension/ui/text.h	Thu Apr 18 22:53:55 2024 +0200
@@ -38,7 +38,7 @@
 typedef struct AscText {
     extend_asc_scene_node;
     cxmutstr text;
-    AscFont const *font;
+    AscFont font;
     asc_col4i color;
     unsigned short max_width;
     unsigned short offx;
--- a/src/ascension/window.h	Thu Apr 18 21:53:53 2024 +0200
+++ b/src/ascension/window.h	Thu Apr 18 22:53:55 2024 +0200
@@ -56,12 +56,6 @@
 } AscWindow;
 
 /**
- * The currently active window in the context.
- * @see asc_window_activate()
- */
-#define asc_window_active asc_context.active_window
-
-/**
  * Initializes the settings structure with default values.
  *
  * @param settings an uninitialized settings object
@@ -78,31 +72,33 @@
  *
  * @param index the index of the new window
  * @param settings the settings to be used for initialization
- * @return a pointer to the window data or \c NULL if initialization failed
  */
-AscWindow *asc_window_initialize(unsigned int index, AscWindowSettings const* settings);
+void asc_window_initialize(unsigned int index, AscWindowSettings const* settings);
 
 /**
  * Destroys the window and its OpenGL context.
  *
- * When this window is currently active, there will be \em no active window afterwards.
+ * When this window is currently active, there
+ * will be \em no active window afterwards.
  *
  * Still alive windows will also be destroyed by asc_context_destroy()
  * automatically.
  *
- * @param window the window
+ * @param index the index of the window
  */
-void asc_window_destroy(AscWindow* window);
+void asc_window_destroy(unsigned int index);
 
 /**
  * Swaps buffers and adjusts the viewport to the current window size.
  *
- * This function is automatically invoked for all initialized windows
- * by asc_loop_next(). You usually do not need to call this function manually.
+ * This function is automatically invoked by asc_loop_next().
+ * You usually should not call this function manually.
  *
- * @param window the window
+ * If this function is invoked on a non-initialized window, nothing happens.
+ *
+ * @param index the index of the window
  */
-void asc_window_sync(AscWindow *window);
+void asc_window_sync(unsigned int index);
 
 /**
  * Switches the active window.
@@ -110,9 +106,9 @@
  * In particular that makes the corresponding OpenGL context "current".
  * When you only want to draw into one window, you'll never need this.
  *
- * @param the window to activate
+ * @param index the index of the window
  */
-void asc_window_activate(AscWindow *window);
+void asc_window_activate(unsigned int index);
 
 #endif /* ASCENSION_WINDOW_H */
 
--- a/src/context.c	Thu Apr 18 21:53:53 2024 +0200
+++ b/src/context.c	Thu Apr 18 22:53:55 2024 +0200
@@ -47,6 +47,10 @@
         return;
     memset(&asc_context, 0, sizeof(AscContext));
 
+    // nothing is active right after initialization
+    asc_context.active_window = ASC_MAX_WINDOWS;
+    asc_context.active_font = ASC_MAX_FONTS;
+
     // initialize error buffer
     cxBufferInit(
             &asc_context.error_buffer,
@@ -72,9 +76,9 @@
 
 void asc_context_destroy(void) {
     for (unsigned int i = 0 ; i < ASC_MAX_WINDOWS ; i++) {
-        asc_window_destroy(&asc_context.windows[i]);
+        asc_window_destroy(i);
     }
-    asc_font_cache_clear();
+    // TODO: fix that fonts are currently leaking by reworking the font cache
 
     // quit SDL
     if (TTF_WasInit())
@@ -143,9 +147,7 @@
 
     // sync the windows
     for (unsigned int i = 0 ; i < ASC_MAX_WINDOWS ; i++) {
-        if (asc_context.windows[i].id > 0) {
-            asc_window_sync(&asc_context.windows[i]);
-        }
+        asc_window_sync(i);
     }
 
     // compute frame time
--- a/src/font.c	Thu Apr 18 21:53:53 2024 +0200
+++ b/src/font.c	Thu Apr 18 22:53:55 2024 +0200
@@ -43,17 +43,19 @@
     }
 }
 
-AscFont const *asc_font(enum AscFontStyle style, int size) {
+void asc_font(enum AscFontStyle style, int size) {
     for (unsigned int i = 0 ; i < asc_context.fonts_loaded ; i++) {
         AscFont *font = &asc_context.fonts[i];
         if (font->size == size && font->style == style) {
-            return font;
+            asc_context.active_font = i;
+            return;
         }
     }
 
     if (asc_context.fonts_loaded == ASC_MAX_FONTS) {
-        asc_dprintf("WARNING: Maximum number of fonts reached, wiping cache!");
-        asc_font_cache_clear();
+        asc_error("Too many fonts. Cannot load more until cache is repaired.");
+        asc_context.active_font = ASC_MAX_FONTS;
+        return;
     }
 
     unsigned int slot = asc_context.fonts_loaded++;
@@ -64,27 +66,8 @@
     if (font->ptr == NULL) {
         asc_context.fonts_loaded--;
         asc_error(TTF_GetError());
-        return NULL;
-    }
-    asc_dprintf("Loaded font size %u, style %u in slot %u", size, style, slot);
-    return font;
-}
-
-void asc_font_cache_clear(void) {
-    asc_dprintf("Fonts in cache that are being unloaded: %u", asc_context.fonts_loaded);
-    while (asc_context.fonts_loaded > 0) {
-        unsigned int i = --asc_context.fonts_loaded;
-        AscFont *font = &asc_context.fonts[i];
-        TTF_CloseFont(font->ptr);
-        font->ptr = NULL;
+    } else {
+        asc_dprintf("Loaded font size %u, style %u in slot %u", size, style, slot);
+        asc_context.active_font = slot;
     }
 }
-
-AscFont const *asc_font_cache_validate(AscFont const *font) {
-    if (font->ptr) {
-        return font;
-    } else {
-        asc_dprintf("Cache miss for font size %u, style %u", font->size, font->style);
-        return asc_font(font->style, font->size);
-    }
-}
--- a/src/primitives.c	Thu Apr 18 21:53:53 2024 +0200
+++ b/src/primitives.c	Thu Apr 18 22:53:55 2024 +0200
@@ -87,7 +87,7 @@
 }
 
 void asc_primitives_draw_plane(void) {
-    AscMesh const *mesh = &(asc_window_active->glctx.primitives.plane);
+    AscMesh const *mesh = &(asc_active_window->glctx.primitives.plane);
     glBindVertexArray(mesh->vao);
     glDrawArrays(GL_TRIANGLE_STRIP, 0, mesh->vertices);
 }
\ No newline at end of file
--- a/src/scene.c	Thu Apr 18 21:53:53 2024 +0200
+++ b/src/scene.c	Thu Apr 18 22:53:55 2024 +0200
@@ -151,7 +151,7 @@
     // Sprites
     // -------
     // TODO: implement view matrix for 2D worlds
-    shader = &asc_window_active->glctx.shader.sprite.base;
+    shader = &asc_active_window->glctx.shader.sprite.base;
     glUseProgram(shader->id);
     glUniformMatrix4fv(shader->projection, 1,
                        GL_FALSE, camera->projection);
--- a/src/text.c	Thu Apr 18 21:53:53 2024 +0200
+++ b/src/text.c	Thu Apr 18 22:53:55 2024 +0200
@@ -36,7 +36,7 @@
 
 static void asc_text_draw(AscText const *node) {
     // Obtain shader
-    AscShaderSprite *shader = &asc_window_active->glctx.shader.sprite;
+    AscShaderSprite *shader = &asc_active_window->glctx.shader.sprite;
 
     // Upload model matrix
     glUniformMatrix4fv(shader->base.model, 1,
@@ -59,7 +59,8 @@
     }
 
     // Render text onto a surface
-    TTF_Font *font = asc_font_cache_validate(node->font)->ptr;
+    // TODO: obtain TTF_Font from font cache once it is repaired
+    TTF_Font *font = node->font.ptr;
     static int alignments[] = {
             TTF_WRAPPED_ALIGN_LEFT,
             TTF_WRAPPED_ALIGN_CENTER,
@@ -101,7 +102,7 @@
     node->base.position.x = (float) args.x;
     node->base.position.y = (float) args.y;
     node->max_width = args.max_width;
-    node->font = asc_context.active_font;
+    node->font = *asc_active_font;
     node->color = asc_context.ink;
     if (args.text == NULL) {
         node->text = cx_mutstr(strdup(" "));
--- a/src/window.c	Thu Apr 18 21:53:53 2024 +0200
+++ b/src/window.c	Thu Apr 18 22:53:55 2024 +0200
@@ -43,16 +43,16 @@
     settings->title = "Ascended Window";
 }
 
-AscWindow *asc_window_initialize(unsigned int index, AscWindowSettings const *settings) {
+void asc_window_initialize(unsigned int index, AscWindowSettings const *settings) {
     if (index >= ASC_MAX_WINDOWS) {
         asc_error("Maximum number of windows exceeded.");
-        return NULL;
+        return;
     }
     AscWindow *window = &asc_context.windows[index];
     if (window->id > 0) {
         asc_error("Cannot create window - slot already occupied.");
         asc_dprintf("Tried to create window with index %u twice", index);
-        return NULL;
+        return;
     }
     if (window->ui != NULL) {
         asc_dprintf("Window with index %u has a dangling UI pointer", index);
@@ -72,7 +72,7 @@
     );
     if (window->window == NULL) {
         asc_error(SDL_GetError());
-        return NULL;
+        return;
     }
 
     window->id = SDL_GetWindowID(window->window);
@@ -85,27 +85,31 @@
     if (asc_gl_context_initialize(&window->glctx, window->window, &settings->glsettings)) {
         window->ui = asc_scene_node_empty();
         asc_dprintf("Window %u initialized", window->id);
-        asc_window_active = window;
-        return window;
+        asc_context.active_window = index;
     } else {
         asc_dprintf("Creating GL context failed for window %u", window->id);
         // cleanup on error
         SDL_DestroyWindow(window->window);
         window->window = NULL;
         window->id = 0;
-        return NULL;
     }
 }
 
-void asc_window_destroy(AscWindow* window) {
+void asc_window_destroy(unsigned int index) {
     // safeguard
-    if (window->id == 0) return;
+    if (asc_context.windows[index].id == 0) return;
 
     // this window cannot be active anymore
-    if (asc_window_active == window) {
-        asc_window_active = NULL;
+    if (asc_context.active_window == index) {
+        asc_context.active_window = ASC_MAX_WINDOWS;
     }
 
+    // pointer to the window
+    AscWindow *window = &asc_context.windows[index];
+
+    // for releasing OpenGL resources, we need to make the context current
+    asc_gl_context_activate(&window->glctx);
+
     // destroy all scenes
     asc_scene_node_free(window->ui);
     window->ui = NULL;
@@ -119,8 +123,8 @@
     }
 
     // if another window was active, make the other context current again
-    if (asc_window_active != NULL) {
-        asc_gl_context_activate(&asc_window_active->glctx);
+    if (asc_context.active_window < ASC_MAX_WINDOWS) {
+        asc_gl_context_activate(&asc_active_window->glctx);
     }
 
     // clean the data
@@ -128,15 +132,19 @@
     memset(window, 0, sizeof(AscWindow));
 }
 
-void asc_window_sync(AscWindow* window) {
-    AscWindow *active_window = asc_window_active;
-    if (window != active_window) {
-        asc_window_activate(window);
+void asc_window_sync(unsigned int index) {
+    // necessary safeguard
+    if (asc_context.windows[index].id == 0) return;
+
+    // active the window that shall be synced temporarily
+    unsigned int active_index = asc_context.active_window;
+    if (index != active_index) {
+        asc_window_activate(index);
     }
 
     // Clear the color buffer for the window frame
-    int window_width = window->dimensions.width;
-    int window_height = window->dimensions.height;
+    int window_width = asc_active_window->dimensions.width;
+    int window_height = asc_active_window->dimensions.height;
     glViewport(0, 0, window_width, window_height);
     glClear(GL_COLOR_BUFFER_BIT);
     asc_recti viewport = {0, 0, window_width, window_height};
@@ -144,20 +152,20 @@
     // Draw the UI
     AscCamera ui_camera;
     asc_camera_ortho(&ui_camera, viewport);
-    asc_scene_draw(window->ui, viewport, &ui_camera);
+    asc_scene_draw(asc_active_window->ui, viewport, &ui_camera);
 
     // Swap Buffers
-    SDL_GL_SwapWindow(window->window);
+    SDL_GL_SwapWindow(asc_active_window->window);
 
     // Clear Flags
-    window->resized = false;
+    asc_active_window->resized = false;
 
-    if (window != active_window) {
-        asc_window_activate(active_window);
+    if (index != active_index) {
+        asc_window_activate(active_index);
     }
 }
 
-void asc_window_activate(AscWindow *window) {
-    asc_gl_context_activate(&window->glctx);
-    asc_window_active = (AscWindow *)window;
+void asc_window_activate(unsigned int index) {
+    asc_context.active_window = index;
+    asc_gl_context_activate(&asc_active_window->glctx);
 }
--- a/test/snake.c	Thu Apr 18 21:53:53 2024 +0200
+++ b/test/snake.c	Thu Apr 18 22:53:55 2024 +0200
@@ -47,7 +47,7 @@
 }
 
 static void create_fps_counter(void) {
-    asc_set_font(asc_font(ASC_FONT_REGULAR, 24));
+    asc_font(ASC_FONT_REGULAR, 24);
     asc_ink_rgb(255, 0, 0);
     AscSceneNode* node = asc_text( .x = 10, .y = 10 );
     asc_scene_add_behavior(node, update_fps_counter);
@@ -56,8 +56,8 @@
 
 static void update_score_counter(AscSceneNode *node) {
     // tie to bottom right of the screen
-    if (asc_window_active->resized) {
-        asc_vec2i bottom_right = asc_window_active->dimensions;
+    if (asc_active_window->resized) {
+        asc_vec2i bottom_right = asc_active_window->dimensions;
         asc_vec2i scale = asc_get_scale2d(node);
         asc_set_position2d(
                 node,
@@ -68,7 +68,7 @@
 }
 
 static void create_score_counter(void) {
-    asc_set_font(asc_font(ASC_FONT_BOLD, 14));
+    asc_font(ASC_FONT_BOLD, 14);
     asc_ink_rgb(0, 255, 0);
     AscSceneNode* node = asc_text(.text = "Score: 0" );
     asc_scene_add_behavior(node, update_score_counter);
@@ -87,7 +87,7 @@
     AscWindowSettings settings;
     asc_window_settings_init_defaults(&settings);
     settings.title = "Snake";
-    AscWindow *window = asc_window_initialize(0, &settings);
+    asc_window_initialize(0, &settings);
 
     // create UI elements
     create_fps_counter();
@@ -98,7 +98,8 @@
         // quit application on any error
         if (asc_has_error()) {
             SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
-                    "Fatal Error", asc_get_error(), window->window);
+                    "Fatal Error", asc_get_error(),
+                    asc_active_window->window);
             asc_clear_error();
             asc_context_quit();
         }

mercurial