diff --git a/apps/scenery/scenes/title.c b/apps/scenery/scenes/title.c index f2f5435..00364ac 100644 --- a/apps/scenery/scenes/title.c +++ b/apps/scenery/scenes/title.c @@ -37,13 +37,8 @@ static void title_tick(State *state) { }, (Color) { 0, 0, 0, 255 } ); - push_text( - text_str, - (Vec2){ 0, 0 }, - text_h, - (Color) { 255, 255, 255, 255 }, - font - ); + push_text(text_str, (Vec2){ 0, 0 }, text_h, (Color) { 255, 255, 255, 255 }, font); + free(text_str); } diff --git a/src/rendering/twn_rendering.c b/src/rendering/twn_rendering.c index a2cf0c8..441c37e 100644 --- a/src/rendering/twn_rendering.c +++ b/src/rendering/twn_rendering.c @@ -22,13 +22,7 @@ Matrix4 camera_look_at_matrix; void render_queue_clear(void) { - /* this doesn't even _deserve_ a TODO */ - /* if you're gonna remove it, this is also being done in main.c */ - for (size_t i = 0; i < arrlenu(ctx.render_queue_2d); ++i) { - if (ctx.render_queue_2d[i].type == PRIMITIVE_2D_TEXT) { - SDL_free(ctx.render_queue_2d[i].text.text); - } - } + text_cache_reset_arena(&ctx.text_cache); /* since i don't intend to free the queues, */ /* it's faster and simpler to just "start over" */ diff --git a/src/rendering/twn_rendering_c.h b/src/rendering/twn_rendering_c.h index 19f886f..8cbb389 100644 --- a/src/rendering/twn_rendering_c.h +++ b/src/rendering/twn_rendering_c.h @@ -154,6 +154,8 @@ void text_cache_init(TextCache *cache); void text_cache_deinit(TextCache *cache); +void text_cache_reset_arena(TextCache *cache); + /* vertex buffer */ VertexBuffer create_vertex_buffer(void); diff --git a/src/rendering/twn_text.c b/src/rendering/twn_text.c index 21fbb35..81fd662 100644 --- a/src/rendering/twn_text.c +++ b/src/rendering/twn_text.c @@ -11,6 +11,88 @@ #define ASCII_END 128 #define NUM_DISPLAY_ASCII ((ASCII_END - ASCII_START) + 1) +/* should be large enough that there's virtually never more than one block */ +#define STRING_ARENA_BLOCK_SIZE 512000 + + +typedef struct StringArenaBlock { + struct StringArenaBlock *next; + char *buffer; + size_t used; +} StringArenaBlock; + + +typedef struct StringArena { + StringArenaBlock *head; + StringArenaBlock *current_block; + char *ptr; +} StringArena; + +static StringArena string_arena; + + +static void string_arena_init(StringArena *arena) { + arena->head = cmalloc(sizeof *arena->head); + arena->current_block = arena->head; + arena->ptr = arena->current_block->buffer; + + arena->current_block->next = NULL; + arena->current_block->buffer = cmalloc(STRING_ARENA_BLOCK_SIZE); + arena->current_block->used = 0; +} + + +static void string_arena_deinit(StringArena *arena) { + StringArenaBlock *p = arena->head; + while (p != NULL) { + StringArenaBlock *block = p; + p = block->next; /* need to keep this before freeing */ + + SDL_free(block->buffer); + SDL_free(block); + } +} + + +static void string_arena_reset(StringArena *arena) { + arena->current_block = arena->head; + arena->current_block->used = 0; + arena->ptr = arena->current_block->buffer; +} + + +static char *string_arena_alloc(StringArena *arena, size_t size) { + /* will never fit! */ + if (size > STRING_ARENA_BLOCK_SIZE) { + /* is there a more graceful way to handle this without risking some sort of horror? */ + CRY("String arena allocation failure", "Tried to allocate a size larger than the block size"); + die_abruptly(); + } + + /* won't fit, we need a new block first */ + if (arena->current_block->used + size > STRING_ARENA_BLOCK_SIZE) { + log_info("String arena block size exceeded, moving on to the next..."); + + /* only allocate if there wasn't one ready to go already */ + if (arena->current_block->next == NULL) { + log_info("Allocating string arena block..."); + + arena->current_block->next = cmalloc(sizeof *arena->head); + arena->current_block->next->next = NULL; + arena->current_block->next->buffer = cmalloc(STRING_ARENA_BLOCK_SIZE); + } + + arena->current_block = arena->current_block->next; + arena->current_block->used = 0; + arena->ptr = arena->current_block->buffer; + } + + char *chunk = arena->ptr; + arena->ptr += size; + arena->current_block->used += size * (sizeof *arena->ptr); + return chunk; +} + static FontData *text_load_font_data(const char *path, int height_px) { FontData *font_data = ccalloc(1, sizeof *font_data); @@ -146,6 +228,7 @@ void render_text(const TextPrimitive *text) { void text_cache_init(TextCache *cache) { arrsetlen(cache->data, 0); + string_arena_init(&string_arena); } @@ -155,6 +238,13 @@ void text_cache_deinit(TextCache *cache) { } arrfree(cache->data); + string_arena_deinit(&string_arena); +} + + +void text_cache_reset_arena(TextCache *cache) { + (void)cache; + string_arena_reset(&string_arena); } @@ -162,8 +252,9 @@ void push_text(char *string, Vec2 position, int height_px, Color color, const ch ensure_font_cache(font_path, height_px); /* the original string might not be around by the time it's used, so copy it */ - /* TODO: arena */ - char *dup_string = SDL_strdup(string); + size_t str_length = SDL_strlen(string) + 1; + char *dup_string = string_arena_alloc(&string_arena, str_length); + SDL_strlcpy(dup_string, string, str_length); TextPrimitive text = { .color = color, diff --git a/src/twn_loop.c b/src/twn_loop.c index 396c3ab..4ff4dab 100644 --- a/src/twn_loop.c +++ b/src/twn_loop.c @@ -371,19 +371,12 @@ static void clean_up(void) { scripting_deinit(ctx); */ - input_state_deinit(&ctx.game.input); - - /* if you're gonna remove this, it's also being done in rendering.c */ - for (size_t i = 0; i < arrlenu(ctx.render_queue_2d); ++i) { - if (ctx.render_queue_2d[i].type == PRIMITIVE_2D_TEXT) { - SDL_free(ctx.render_queue_2d[i].text.text); - } - } - arrfree(ctx.render_queue_2d); - + input_state_deinit(&ctx.game.input); text_cache_deinit(&ctx.text_cache); textures_cache_deinit(&ctx.texture_cache); + arrfree(ctx.render_queue_2d); + PHYSFS_deinit(); SDL_Quit(); }