From 2af8b1cad407815c809434e691b8789e78d0b953 Mon Sep 17 00:00:00 2001 From: asrael Date: Sat, 6 Dec 2025 15:04:53 -0600 Subject: [PATCH] refactor: add helpers for file I/O, main script detection, and safe strncpy --- demo/cart.fnl | 4 ++ pxl8.sh | 1 - src/pxl8.c | 118 +++++++++++++++++++-------------------- src/pxl8_bsp.c | 27 ++------- src/pxl8_cart.c | 112 ++++++++++++++++++++++--------------- src/pxl8_cart.h | 9 ++- src/pxl8_gfx.c | 18 ++---- src/pxl8_gfx.h | 2 - src/pxl8_hal.h | 14 ++--- src/pxl8_macros.h | 9 +++ src/pxl8_rec.c | 137 ---------------------------------------------- src/pxl8_rec.h | 31 ----------- src/pxl8_script.c | 107 ++++++++++++++++++++++++++++++++---- src/pxl8_script.h | 2 + src/pxl8_sdl3.c | 134 ++++++++------------------------------------- 15 files changed, 282 insertions(+), 443 deletions(-) create mode 100644 demo/cart.fnl delete mode 100644 src/pxl8_rec.c delete mode 100644 src/pxl8_rec.h diff --git a/demo/cart.fnl b/demo/cart.fnl new file mode 100644 index 0000000..bb0e721 --- /dev/null +++ b/demo/cart.fnl @@ -0,0 +1,4 @@ +{:title "pxl8 demo" + :resolution "640x360" + :window-size [1280 720] + :pixel-mode "indexed"} diff --git a/pxl8.sh b/pxl8.sh index 11ac307..f7257e3 100755 --- a/pxl8.sh +++ b/pxl8.sh @@ -343,7 +343,6 @@ case "$COMMAND" in src/pxl8_io.c src/pxl8_log.c src/pxl8_math.c - src/pxl8_rec.c src/pxl8_repl.c src/pxl8_save.c src/pxl8_script.c diff --git a/src/pxl8.c b/src/pxl8.c index 2057ad6..af409c6 100644 --- a/src/pxl8.c +++ b/src/pxl8.c @@ -9,14 +9,13 @@ #include #include -#include "pxl8_cart.h" #include "pxl8_game.h" #include "pxl8_hal.h" #include "pxl8_log.h" +#include "pxl8_macros.h" #include "pxl8_repl.h" #include "pxl8_script.h" #include "pxl8_sys.h" -#include "pxl8_types.h" struct pxl8 { pxl8_cart* cart; @@ -65,8 +64,6 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { if (!sys || !sys->game) return PXL8_ERROR_INVALID_ARGUMENT; pxl8_game* game = sys->game; - pxl8_pixel_mode pixel_mode = PXL8_PIXEL_INDEXED; - pxl8_resolution resolution = PXL8_RESOLUTION_640x360; const char* script_arg = NULL; bool bundle_mode = false; bool pack_mode = false; @@ -116,13 +113,61 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { return result; } - if (game->repl_mode) { - pxl8_info("starting in REPL mode with script: %s", game->script_path); - } - pxl8_info("starting up"); - sys->platform_data = sys->hal->create(pixel_mode, resolution, "pxl8", 1280, 720); + game->script = pxl8_script_create(game->repl_mode); + if (!game->script) { + pxl8_error("failed to initialize scripting: %s", pxl8_script_get_last_error(game->script)); + return PXL8_ERROR_INITIALIZATION_FAILED; + } + + bool has_embedded = pxl8_cart_has_embedded(argv[0]); + const char* cart_path = script_arg; + char* original_cwd = getcwd(NULL, 0); + + bool load_embedded = has_embedded && !script_arg; + bool load_from_path = false; + + if (!load_embedded && (cart_path || !has_embedded)) { + if (!cart_path) cart_path = "demo"; + struct stat st; + load_from_path = (stat(cart_path, &st) == 0 && S_ISDIR(st.st_mode)) || + (cart_path && strstr(cart_path, ".pxc")); + } + + if (load_embedded || load_from_path) { + sys->cart = pxl8_cart_create(); + pxl8_result load_result = load_embedded + ? pxl8_cart_load_embedded(sys->cart, argv[0]) + : pxl8_cart_load(sys->cart, cart_path); + + if (!sys->cart || load_result != PXL8_OK) { + pxl8_error("failed to load cart%s%s", load_from_path ? ": " : "", load_from_path ? cart_path : ""); + if (sys->cart) pxl8_cart_destroy(sys->cart); + sys->cart = NULL; + free(original_cwd); + return PXL8_ERROR_INITIALIZATION_FAILED; + } + + pxl8_cart_mount(sys->cart); + pxl8_script_load_cart_manifest(game->script, sys->cart); + if (load_from_path) { + pxl8_script_set_cart_path(game->script, pxl8_cart_get_base_path(sys->cart), original_cwd); + } + pxl8_strncpy(game->script_path, "main.fnl", sizeof(game->script_path)); + } else if (script_arg) { + pxl8_strncpy(game->script_path, script_arg, sizeof(game->script_path)); + } + free(original_cwd); + + const char* window_title = pxl8_cart_get_title(sys->cart); + if (!window_title) window_title = "pxl8"; + pxl8_resolution resolution = pxl8_cart_get_resolution(sys->cart); + pxl8_pixel_mode pixel_mode = pxl8_cart_get_pixel_mode(sys->cart); + pxl8_size window_size = pxl8_cart_get_window_size(sys->cart); + pxl8_size render_size = pxl8_get_resolution_dimensions(resolution); + + sys->platform_data = sys->hal->create(render_size.w, render_size.h, window_title, window_size.w, window_size.h); if (!sys->platform_data) { pxl8_error("failed to create platform context"); return PXL8_ERROR_INITIALIZATION_FAILED; @@ -139,58 +184,8 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { return PXL8_ERROR_INITIALIZATION_FAILED; } - game->script = pxl8_script_create(game->repl_mode); - if (!game->script) { - pxl8_error("failed to initialize scripting: %s", pxl8_script_get_last_error(game->script)); - return PXL8_ERROR_INITIALIZATION_FAILED; - } - - bool has_embedded = pxl8_cart_has_embedded(argv[0]); - const char* cart_path = script_arg; - - if (has_embedded && !script_arg) { - sys->cart = pxl8_cart_create(); - if (!sys->cart) { - pxl8_error("failed to create cart"); - return PXL8_ERROR_INITIALIZATION_FAILED; - } - if (pxl8_cart_load_embedded(sys->cart, argv[0]) == PXL8_OK) { - pxl8_cart_mount(sys->cart); - strncpy(game->script_path, "main.fnl", sizeof(game->script_path) - 1); - game->script_path[sizeof(game->script_path) - 1] = '\0'; - pxl8_info("running embedded cart"); - } else { - pxl8_error("failed to load embedded cart"); - return PXL8_ERROR_INITIALIZATION_FAILED; - } - } else if (cart_path || !has_embedded) { - if (!cart_path) cart_path = "demo"; - - struct stat st; - bool is_cart = (stat(cart_path, &st) == 0 && S_ISDIR(st.st_mode)) || - (cart_path && strstr(cart_path, ".pxc")); - - if (is_cart) { - char* original_cwd = getcwd(NULL, 0); - sys->cart = pxl8_cart_create(); - if (!sys->cart) { - pxl8_error("failed to create cart"); - return PXL8_ERROR_INITIALIZATION_FAILED; - } - if (pxl8_cart_load(sys->cart, cart_path) == PXL8_OK) { - pxl8_script_set_cart_path(game->script, pxl8_cart_get_base_path(sys->cart), original_cwd); - pxl8_cart_mount(sys->cart); - strncpy(game->script_path, "main.fnl", sizeof(game->script_path) - 1); - game->script_path[sizeof(game->script_path) - 1] = '\0'; - } else { - pxl8_error("failed to load cart: %s", cart_path); - return PXL8_ERROR_INITIALIZATION_FAILED; - } - free(original_cwd); - } else if (script_arg) { - strncpy(game->script_path, script_arg, sizeof(game->script_path) - 1); - game->script_path[sizeof(game->script_path) - 1] = '\0'; - } + if (game->repl_mode) { + pxl8_info("starting in REPL mode with script: %s", game->script_path); } pxl8_script_set_gfx(game->script, game->gfx); @@ -305,7 +300,6 @@ pxl8_result pxl8_frame(pxl8* sys) { pxl8_gfx_set_viewport(game->gfx, pxl8_gfx_viewport(bounds, pxl8_gfx_get_width(game->gfx), pxl8_gfx_get_height(game->gfx))); pxl8_gfx_upload_framebuffer(game->gfx); - pxl8_gfx_upload_atlas(game->gfx); pxl8_gfx_present(game->gfx); memset(game->input.keys_pressed, 0, sizeof(game->input.keys_pressed)); diff --git a/src/pxl8_bsp.c b/src/pxl8_bsp.c index f7a3b37..c73e4c2 100644 --- a/src/pxl8_bsp.c +++ b/src/pxl8_bsp.c @@ -98,31 +98,14 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { memset(bsp, 0, sizeof(*bsp)); - FILE* f = fopen(path, "rb"); - if (!f) { + u8* file_data = NULL; + size_t file_size = 0; + pxl8_result result = pxl8_io_read_binary_file(path, &file_data, &file_size); + if (result != PXL8_OK) { pxl8_error("Failed to load BSP file: %s", path); - return PXL8_ERROR_FILE_NOT_FOUND; + return result; } - fseek(f, 0, SEEK_END); - size_t file_size = ftell(f); - fseek(f, 0, SEEK_SET); - - u8* file_data = (u8*)malloc(file_size); - if (!file_data) { - fclose(f); - pxl8_error("Failed to allocate memory for BSP file: %s", path); - return PXL8_ERROR_OUT_OF_MEMORY; - } - - if (fread(file_data, 1, file_size, f) != file_size) { - free(file_data); - fclose(f); - pxl8_error("Failed to read BSP file: %s", path); - return PXL8_ERROR_INVALID_FORMAT; - } - fclose(f); - if (file_size < sizeof(pxl8_bsp_header)) { pxl8_error("BSP file too small: %s", path); free(file_data); diff --git a/src/pxl8_cart.c b/src/pxl8_cart.c index c9620ff..682e262 100644 --- a/src/pxl8_cart.c +++ b/src/pxl8_cart.c @@ -45,7 +45,10 @@ struct pxl8_cart { pxl8_cart_file* files; u32 file_count; char* base_path; - char* name; + char* title; + pxl8_resolution resolution; + pxl8_size window_size; + pxl8_pixel_mode pixel_mode; bool is_folder; bool is_mounted; }; @@ -63,19 +66,12 @@ static bool is_pxc_file(const char* path) { return len > 4 && strcmp(path + len - 4, ".pxc") == 0; } -static char* get_cart_name(const char* path) { - char* name = strdup(path); - size_t len = strlen(name); - if (len > 4 && strcmp(name + len - 4, ".pxc") == 0) { - name[len - 4] = '\0'; - } - char* last_slash = strrchr(name, '/'); - if (last_slash) { - char* result = strdup(last_slash + 1); - free(name); - return result; - } - return name; +static bool has_main_script(const char* base_path) { + char path[512]; + snprintf(path, sizeof(path), "%s/main.fnl", base_path); + if (access(path, F_OK) == 0) return true; + snprintf(path, sizeof(path), "%s/main.lua", base_path); + return access(path, F_OK) == 0; } static void collect_files_recursive(const char* dir_path, const char* prefix, @@ -155,7 +151,13 @@ static pxl8_result load_packed_cart(pxl8_cart* cart, const u8* data, u32 size) { } pxl8_cart* pxl8_cart_create(void) { - return calloc(1, sizeof(pxl8_cart)); + pxl8_cart* cart = calloc(1, sizeof(pxl8_cart)); + if (cart) { + cart->resolution = PXL8_RESOLUTION_640x360; + cart->window_size = (pxl8_size){1280, 720}; + cart->pixel_mode = PXL8_PIXEL_INDEXED; + } + return cart; } pxl8_cart* pxl8_cart_current(void) { @@ -171,7 +173,6 @@ void pxl8_cart_destroy(pxl8_cart* cart) { pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) { if (!cart || !path) return PXL8_ERROR_NULL_POINTER; pxl8_cart_unload(cart); - cart->name = get_cart_name(path); if (is_directory(path)) { cart->base_path = realpath(path, NULL); @@ -181,17 +182,12 @@ pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) { } cart->is_folder = true; - char main_path[512]; - snprintf(main_path, sizeof(main_path), "%s/main.fnl", cart->base_path); - if (access(main_path, F_OK) != 0) { - snprintf(main_path, sizeof(main_path), "%s/main.lua", cart->base_path); - if (access(main_path, F_OK) != 0) { - pxl8_error("No main.fnl or main.lua found in cart: %s", path); - return PXL8_ERROR_FILE_NOT_FOUND; - } + if (!has_main_script(cart->base_path)) { + pxl8_error("No main.fnl or main.lua found in cart: %s", path); + return PXL8_ERROR_FILE_NOT_FOUND; } - pxl8_info("Loaded cart folder: %s", cart->name); + pxl8_info("Loaded cart"); return PXL8_OK; } @@ -218,7 +214,7 @@ pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) { free(data); if (result == PXL8_OK) { - pxl8_info("Loaded cart: %s", cart->name); + pxl8_info("Loaded cart"); } return result; } @@ -254,12 +250,11 @@ pxl8_result pxl8_cart_load_embedded(pxl8_cart* cart, const char* exe_path) { } fclose(file); - cart->name = get_cart_name(exe_path); pxl8_result result = load_packed_cart(cart, data, trailer.cart_size); free(data); if (result == PXL8_OK) { - pxl8_info("Loaded embedded cart: %s", cart->name); + pxl8_info("Loaded embedded cart"); } return result; } @@ -281,10 +276,12 @@ void pxl8_cart_unload(pxl8_cart* cart) { cart->data = NULL; cart->data_size = 0; - if (cart->name) { - pxl8_info("Unloaded cart: %s", cart->name); - free(cart->name); - cart->name = NULL; + if (cart->title) { + pxl8_info("Unloaded cart: %s", cart->title); + free(cart->title); + cart->title = NULL; + } else { + pxl8_info("Unloaded cart"); } free(cart->base_path); @@ -312,7 +309,11 @@ pxl8_result pxl8_cart_mount(pxl8_cart* cart) { cart->is_mounted = true; pxl8_current_cart = cart; - pxl8_info("Mounted cart: %s", cart->name); + if (cart->title) { + pxl8_info("Mounted cart: %s", cart->title); + } else { + pxl8_info("Mounted cart"); + } return PXL8_OK; } @@ -335,8 +336,38 @@ const char* pxl8_cart_get_base_path(const pxl8_cart* cart) { return cart ? cart->base_path : NULL; } -const char* pxl8_cart_get_name(const pxl8_cart* cart) { - return cart ? cart->name : NULL; +const char* pxl8_cart_get_title(const pxl8_cart* cart) { + return cart ? cart->title : NULL; +} + +void pxl8_cart_set_title(pxl8_cart* cart, const char* title) { + if (!cart || !title) return; + free(cart->title); + cart->title = strdup(title); +} + +pxl8_resolution pxl8_cart_get_resolution(const pxl8_cart* cart) { + return cart ? cart->resolution : PXL8_RESOLUTION_640x360; +} + +void pxl8_cart_set_resolution(pxl8_cart* cart, pxl8_resolution resolution) { + if (cart) cart->resolution = resolution; +} + +pxl8_size pxl8_cart_get_window_size(const pxl8_cart* cart) { + return cart ? cart->window_size : (pxl8_size){1280, 720}; +} + +void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size) { + if (cart) cart->window_size = size; +} + +pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart) { + return cart ? cart->pixel_mode : PXL8_PIXEL_INDEXED; +} + +void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode) { + if (cart) cart->pixel_mode = mode; } bool pxl8_cart_is_packed(const pxl8_cart* cart) { @@ -429,14 +460,9 @@ pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) { return PXL8_ERROR_FILE_NOT_FOUND; } - char main_path[512]; - snprintf(main_path, sizeof(main_path), "%s/main.fnl", folder_path); - if (access(main_path, F_OK) != 0) { - snprintf(main_path, sizeof(main_path), "%s/main.lua", folder_path); - if (access(main_path, F_OK) != 0) { - pxl8_error("No main.fnl or main.lua found in cart: %s", folder_path); - return PXL8_ERROR_FILE_NOT_FOUND; - } + if (!has_main_script(folder_path)) { + pxl8_error("No main.fnl or main.lua found in cart: %s", folder_path); + return PXL8_ERROR_FILE_NOT_FOUND; } char** paths = NULL; diff --git a/src/pxl8_cart.h b/src/pxl8_cart.h index c9b5f0b..16b5914 100644 --- a/src/pxl8_cart.h +++ b/src/pxl8_cart.h @@ -22,7 +22,14 @@ pxl8_result pxl8_cart_mount(pxl8_cart* cart); void pxl8_cart_unmount(pxl8_cart* cart); const char* pxl8_cart_get_base_path(const pxl8_cart* cart); -const char* pxl8_cart_get_name(const pxl8_cart* cart); +const char* pxl8_cart_get_title(const pxl8_cart* cart); +pxl8_resolution pxl8_cart_get_resolution(const pxl8_cart* cart); +pxl8_size pxl8_cart_get_window_size(const pxl8_cart* cart); +pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart); +void pxl8_cart_set_title(pxl8_cart* cart, const char* title); +void pxl8_cart_set_resolution(pxl8_cart* cart, pxl8_resolution resolution); +void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size); +void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode); bool pxl8_cart_is_packed(const pxl8_cart* cart); bool pxl8_cart_has_embedded(const char* exe_path); diff --git a/src/pxl8_gfx.c b/src/pxl8_gfx.c index c4aba1d..027142f 100644 --- a/src/pxl8_gfx.c +++ b/src/pxl8_gfx.c @@ -10,6 +10,7 @@ #include "pxl8_font.h" #include "pxl8_hal.h" #include "pxl8_log.h" +#include "pxl8_macros.h" #include "pxl8_math.h" #include "pxl8_sys.h" #include "pxl8_types.h" @@ -273,8 +274,7 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) { pxl8_sprite_cache_entry* entry = &gfx->sprite_cache[gfx->sprite_cache_count++]; entry->active = true; entry->sprite_id = sprite_id; - strncpy(entry->path, path, sizeof(entry->path) - 1); - entry->path[sizeof(entry->path) - 1] = '\0'; + pxl8_strncpy(entry->path, path, sizeof(entry->path)); return sprite_id; } @@ -327,25 +327,17 @@ pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) { return PXL8_OK; } -void pxl8_gfx_upload_atlas(pxl8_gfx* gfx) { - if (!gfx || !gfx->initialized || !gfx->atlas) return; - - if (gfx->hal && gfx->hal->upload_atlas) { - gfx->hal->upload_atlas(gfx->platform_data, gfx->atlas, gfx->palette, gfx->pixel_mode); - pxl8_atlas_mark_clean(gfx->atlas); - } -} - void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) { if (!gfx || !gfx->initialized || !gfx->hal) return; - gfx->hal->upload_framebuffer( + u32 bpp = (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) ? 2 : 1; + gfx->hal->upload_texture( gfx->platform_data, gfx->framebuffer, gfx->framebuffer_width, gfx->framebuffer_height, gfx->palette, - gfx->pixel_mode + bpp ); } diff --git a/src/pxl8_gfx.h b/src/pxl8_gfx.h index 3743acc..e83f0a5 100644 --- a/src/pxl8_gfx.h +++ b/src/pxl8_gfx.h @@ -4,7 +4,6 @@ #include "pxl8_math.h" #include "pxl8_types.h" -typedef struct pxl8_atlas pxl8_atlas; typedef struct pxl8_gfx pxl8_gfx; typedef struct pxl8_vertex { @@ -42,7 +41,6 @@ pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path); pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path); void pxl8_gfx_present(pxl8_gfx* gfx); void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom); -void pxl8_gfx_upload_atlas(pxl8_gfx* gfx); void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx); pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height); diff --git a/src/pxl8_hal.h b/src/pxl8_hal.h index fa10532..fa52be8 100644 --- a/src/pxl8_hal.h +++ b/src/pxl8_hal.h @@ -2,11 +2,8 @@ #include "pxl8_types.h" -typedef struct pxl8_atlas pxl8_atlas; -typedef struct pxl8_game pxl8_game; - typedef struct pxl8_hal { - void* (*create)(pxl8_pixel_mode mode, pxl8_resolution res, + void* (*create)(i32 render_w, i32 render_h, const char* title, i32 win_w, i32 win_h); void (*destroy)(void* platform_data); @@ -14,11 +11,8 @@ typedef struct pxl8_hal { void (*center_cursor)(void* platform_data); void (*present)(void* platform_data); - void (*set_cursor)(void* platform_data, pxl8_cursor cursor); + void (*set_cursor)(void* platform_data, u32 cursor); void (*set_relative_mouse_mode)(void* platform_data, bool enabled); - void (*upload_atlas)(void* platform_data, const pxl8_atlas* atlas, - const u32* palette, pxl8_pixel_mode mode); - void (*upload_framebuffer)(void* platform_data, const u8* fb, - i32 w, i32 h, const u32* palette, - pxl8_pixel_mode mode); + void (*upload_texture)(void* platform_data, const u8* pixels, u32 w, u32 h, + const u32* palette, u32 bpp); } pxl8_hal; diff --git a/src/pxl8_macros.h b/src/pxl8_macros.h index 5fb1532..ed3509d 100644 --- a/src/pxl8_macros.h +++ b/src/pxl8_macros.h @@ -1,5 +1,7 @@ #pragma once +#include + #ifndef pxl8_min #define pxl8_min(a, b) ((a) < (b) ? (a) : (b)) #endif @@ -7,3 +9,10 @@ #ifndef pxl8_max #define pxl8_max(a, b) ((a) > (b) ? (a) : (b)) #endif + +#ifndef pxl8_strncpy +#define pxl8_strncpy(dst, src, size) do { \ + strncpy((dst), (src), (size) - 1); \ + (dst)[(size) - 1] = '\0'; \ +} while (0) +#endif diff --git a/src/pxl8_rec.c b/src/pxl8_rec.c deleted file mode 100644 index 9c750ec..0000000 --- a/src/pxl8_rec.c +++ /dev/null @@ -1,137 +0,0 @@ -#include "pxl8_rec.h" - -#include "pxl8_log.h" - -#include -#include -#include -#include - -struct pxl8_recorder { - i32 height; - i32 width; - - pxl8_recorder_format format; - i32 framerate; - char output_path[512]; - - FILE* ffmpeg_pipe; - u32 frame_count; - bool recording; -}; - -static void generate_default_output_path(pxl8_recorder* rec) { - time_t now = time(NULL); - struct tm* t = localtime(&now); - const char* ext = (rec->format == PXL8_RECORDER_GIF) ? "gif" : "mp4"; - - snprintf(rec->output_path, sizeof(rec->output_path), - "recording_%04d%02d%02d_%02d%02d%02d.%s", - t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, - t->tm_hour, t->tm_min, t->tm_sec, ext); -} - -pxl8_recorder* pxl8_recorder_create(i32 width, i32 height) { - pxl8_recorder* rec = (pxl8_recorder*)calloc(1, sizeof(pxl8_recorder)); - if (!rec) return NULL; - - rec->height = height; - rec->width = width; - - rec->format = PXL8_RECORDER_MP4; - rec->framerate = 60; - rec->output_path[0] = '\0'; - - rec->ffmpeg_pipe = NULL; - rec->frame_count = 0; - rec->recording = false; - - return rec; -} - -void pxl8_recorder_destroy(pxl8_recorder* rec) { - if (!rec) return; - if (rec->recording) pxl8_recorder_stop(rec); - free(rec); -} - -void pxl8_recorder_set_format(pxl8_recorder* rec, pxl8_recorder_format format) { - if (rec && !rec->recording) rec->format = format; -} - -void pxl8_recorder_set_framerate(pxl8_recorder* rec, i32 fps) { - if (rec && !rec->recording) rec->framerate = fps > 0 ? fps : 60; -} - -void pxl8_recorder_set_output_path(pxl8_recorder* rec, const char* path) { - if (rec && !rec->recording && path) strncpy(rec->output_path, path, sizeof(rec->output_path) - 1); -} - -void pxl8_recorder_start(pxl8_recorder* rec) { - if (!rec || rec->recording) return; - if (rec->output_path[0] == '\0') generate_default_output_path(rec); - - char cmd[1024]; - - if (rec->format == PXL8_RECORDER_GIF) { - snprintf(cmd, sizeof(cmd), - "ffmpeg -y -f rawvideo -pix_fmt rgba -s %dx%d -r %d -i - " - "-vf \"split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse\" " - "\"%s\" 2>/dev/null", - rec->width, rec->height, rec->framerate, rec->output_path); - } else { - snprintf(cmd, sizeof(cmd), - "ffmpeg -y -f rawvideo -pix_fmt rgba -s %dx%d -r %d -i - " - "-c:v libx264 -pix_fmt yuv420p -crf 18 -preset fast " - "\"%s\" 2>/dev/null", - rec->width, rec->height, rec->framerate, rec->output_path); - } - - rec->ffmpeg_pipe = popen(cmd, "w"); - if (!rec->ffmpeg_pipe) { - pxl8_error("Failed to start ffmpeg. Is ffmpeg installed?"); - return; - } - - rec->frame_count = 0; - rec->recording = true; - - pxl8_info("Recording started: %s (%dx%d @ %dfps)", - rec->output_path, rec->width, rec->height, rec->framerate); -} - -void pxl8_recorder_stop(pxl8_recorder* rec) { - if (!rec || !rec->recording) return; - - rec->recording = false; - - if (rec->ffmpeg_pipe) { - pclose(rec->ffmpeg_pipe); - rec->ffmpeg_pipe = NULL; - } - - pxl8_info("Recording stopped: %u frames -> %s", rec->frame_count, rec->output_path); -} - -bool pxl8_recorder_is_recording(pxl8_recorder* rec) { - return rec && rec->recording; -} - -void pxl8_recorder_capture_frame(pxl8_recorder* rec, const u32* rgba_pixels) { - if (!rec || !rec->recording || !rgba_pixels || !rec->ffmpeg_pipe) return; - - size_t written = fwrite(rgba_pixels, 4, rec->width * rec->height, rec->ffmpeg_pipe); - if (written == (size_t)(rec->width * rec->height)) { - rec->frame_count++; - } else { - pxl8_error("Failed to write frame %u", rec->frame_count); - } -} - -u32 pxl8_recorder_get_frame_count(pxl8_recorder* rec) { - return rec ? rec->frame_count : 0; -} - -const char* pxl8_recorder_get_output_path(pxl8_recorder* rec) { - return rec ? rec->output_path : NULL; -} diff --git a/src/pxl8_rec.h b/src/pxl8_rec.h deleted file mode 100644 index 4ba5dca..0000000 --- a/src/pxl8_rec.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "pxl8_types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum pxl8_recorder_format { - PXL8_RECORDER_GIF, - PXL8_RECORDER_MP4 -} pxl8_recorder_format; - -typedef struct pxl8_recorder pxl8_recorder; - -pxl8_recorder* pxl8_recorder_create(i32 width, i32 height); -void pxl8_recorder_destroy(pxl8_recorder* rec); - -void pxl8_recorder_capture_frame(pxl8_recorder* rec, const u32* rgba_pixels); -u32 pxl8_recorder_get_frame_count(pxl8_recorder* rec); -const char* pxl8_recorder_get_output_path(pxl8_recorder* rec); -bool pxl8_recorder_is_recording(pxl8_recorder* rec); -void pxl8_recorder_set_format(pxl8_recorder* rec, pxl8_recorder_format format); -void pxl8_recorder_set_framerate(pxl8_recorder* rec, i32 fps); -void pxl8_recorder_set_output_path(pxl8_recorder* rec, const char* path); -void pxl8_recorder_start(pxl8_recorder* rec); -void pxl8_recorder_stop(pxl8_recorder* rec); - -#ifdef __cplusplus -} -#endif diff --git a/src/pxl8_script.c b/src/pxl8_script.c index 28be5ed..58de903 100644 --- a/src/pxl8_script.c +++ b/src/pxl8_script.c @@ -14,8 +14,9 @@ #include "pxl8_cart.h" #include "pxl8_embed.h" -#include "pxl8_log.h" #include "pxl8_gui.h" +#include "pxl8_log.h" +#include "pxl8_macros.h" struct pxl8_script { lua_State* L; @@ -193,7 +194,6 @@ static const char* pxl8_ffi_cdefs = "i32 pxl8_gfx_load_palette(pxl8_gfx* ctx, const char* filepath);\n" "i32 pxl8_gfx_load_sprite(pxl8_gfx* ctx, const char* filepath, u32* sprite_id);\n" "i32 pxl8_gfx_create_texture(pxl8_gfx* ctx, const u8* pixels, u32 width, u32 height);\n" -"void pxl8_gfx_upload_atlas(pxl8_gfx* ctx);\n" "typedef struct pxl8_input_state pxl8_input_state;\n" "bool pxl8_key_down(const pxl8_input_state* input, const char* key_name);\n" "bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name);\n" @@ -422,11 +422,11 @@ static const char* pxl8_ffi_cdefs = void pxl8_lua_log(int level, const char* file, int line, const char* msg) { if (file && (file[0] == '?' || file[0] == '\0')) file = NULL; switch (level) { - case 0: pxl8_log_write_info("%s", msg); break; - case 1: pxl8_log_write_warn(file, line, "%s", msg); break; - case 2: pxl8_log_write_error(file, line, "%s", msg); break; - case 3: pxl8_log_write_debug(file, line, "%s", msg); break; - case 4: pxl8_log_write_trace(file, line, "%s", msg); break; + case PXL8_LOG_LEVEL_TRACE: pxl8_log_write_trace(file, line, "%s", msg); break; + case PXL8_LOG_LEVEL_DEBUG: pxl8_log_write_debug(file, line, "%s", msg); break; + case PXL8_LOG_LEVEL_INFO: pxl8_log_write_info("%s", msg); break; + case PXL8_LOG_LEVEL_WARN: pxl8_log_write_warn(file, line, "%s", msg); break; + case PXL8_LOG_LEVEL_ERROR: pxl8_log_write_error(file, line, "%s", msg); break; } } @@ -610,8 +610,7 @@ void pxl8_script_set_sys(pxl8_script* script, void* sys) { static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* filename, char* out_basename, size_t basename_size) { char filename_copy[PATH_MAX]; - strncpy(filename_copy, filename, sizeof(filename_copy) - 1); - filename_copy[sizeof(filename_copy) - 1] = '\0'; + pxl8_strncpy(filename_copy, filename, sizeof(filename_copy)); char* last_slash = strrchr(filename_copy, '/'); @@ -933,8 +932,7 @@ static time_t get_latest_script_mod_time(const char* dir_path) { pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path) { if (!script || !path) return PXL8_ERROR_NULL_POINTER; - strncpy(script->main_path, path, sizeof(script->main_path) - 1); - script->main_path[sizeof(script->main_path) - 1] = '\0'; + pxl8_strncpy(script->main_path, path, sizeof(script->main_path)); char* last_slash = strrchr(script->main_path, '/'); if (last_slash) { @@ -1006,3 +1004,90 @@ bool pxl8_script_is_incomplete_input(pxl8_script* script) { if (!script) return false; return pxl8_script_is_incomplete_error(script->last_error); } + +static pxl8_resolution parse_resolution(const char* str) { + if (strcmp(str, "240x160") == 0) return PXL8_RESOLUTION_240x160; + if (strcmp(str, "320x180") == 0) return PXL8_RESOLUTION_320x180; + if (strcmp(str, "320x240") == 0) return PXL8_RESOLUTION_320x240; + if (strcmp(str, "640x360") == 0) return PXL8_RESOLUTION_640x360; + if (strcmp(str, "640x480") == 0) return PXL8_RESOLUTION_640x480; + if (strcmp(str, "800x600") == 0) return PXL8_RESOLUTION_800x600; + if (strcmp(str, "960x540") == 0) return PXL8_RESOLUTION_960x540; + return PXL8_RESOLUTION_640x360; +} + +static pxl8_pixel_mode parse_pixel_mode(const char* str) { + if (strcmp(str, "indexed") == 0) return PXL8_PIXEL_INDEXED; + if (strcmp(str, "hicolor") == 0) return PXL8_PIXEL_HICOLOR; + return PXL8_PIXEL_INDEXED; +} + +pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart) { + if (!script || !script->L || !cart) return PXL8_ERROR_NULL_POINTER; + + if (!pxl8_cart_file_exists(cart, "cart.fnl")) { + return PXL8_OK; + } + + u8* data = NULL; + u32 size = 0; + if (pxl8_cart_read_file(cart, "cart.fnl", &data, &size) != PXL8_OK) { + return PXL8_OK; + } + + lua_getglobal(script->L, "fennel"); + if (lua_isnil(script->L, -1)) { + lua_pop(script->L, 1); + pxl8_cart_free_file(data); + return PXL8_OK; + } + + lua_getfield(script->L, -1, "eval"); + lua_pushlstring(script->L, (const char*)data, size); + lua_createtable(script->L, 0, 1); + lua_pushstring(script->L, "cart.fnl"); + lua_setfield(script->L, -2, "filename"); + + pxl8_cart_free_file(data); + + if (lua_pcall(script->L, 2, 1, 0) != 0) { + pxl8_warn("Failed to load cart.fnl: %s", lua_tostring(script->L, -1)); + lua_pop(script->L, 2); + return PXL8_OK; + } + + if (lua_istable(script->L, -1)) { + lua_getfield(script->L, -1, "title"); + if (lua_isstring(script->L, -1)) { + pxl8_cart_set_title(cart, lua_tostring(script->L, -1)); + } + lua_pop(script->L, 1); + + lua_getfield(script->L, -1, "resolution"); + if (lua_isstring(script->L, -1)) { + pxl8_cart_set_resolution(cart, parse_resolution(lua_tostring(script->L, -1))); + } + lua_pop(script->L, 1); + + lua_getfield(script->L, -1, "pixel-mode"); + if (lua_isstring(script->L, -1)) { + pxl8_cart_set_pixel_mode(cart, parse_pixel_mode(lua_tostring(script->L, -1))); + } + lua_pop(script->L, 1); + + lua_getfield(script->L, -1, "window-size"); + if (lua_istable(script->L, -1)) { + lua_rawgeti(script->L, -1, 1); + lua_rawgeti(script->L, -2, 2); + if (lua_isnumber(script->L, -2) && lua_isnumber(script->L, -1)) { + pxl8_size size = {(i32)lua_tonumber(script->L, -2), (i32)lua_tonumber(script->L, -1)}; + pxl8_cart_set_window_size(cart, size); + } + lua_pop(script->L, 2); + } + lua_pop(script->L, 1); + } + + lua_pop(script->L, 2); + return PXL8_OK; +} diff --git a/src/pxl8_script.h b/src/pxl8_script.h index d47a467..3aefaf1 100644 --- a/src/pxl8_script.h +++ b/src/pxl8_script.h @@ -1,5 +1,6 @@ #pragma once +#include "pxl8_cart.h" #include "pxl8_gfx.h" #include "pxl8_types.h" @@ -25,6 +26,7 @@ pxl8_result pxl8_script_call_function_f32(pxl8_script* script, const char* name, bool pxl8_script_check_reload(pxl8_script* script); pxl8_result pxl8_script_eval(pxl8_script* script, const char* code); pxl8_result pxl8_script_eval_repl(pxl8_script* script, const char* code); +pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart); pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path); pxl8_result pxl8_script_load_module(pxl8_script* script, const char* module_name); pxl8_result pxl8_script_run_fennel_file(pxl8_script* script, const char* filename); diff --git a/src/pxl8_sdl3.c b/src/pxl8_sdl3.c index e009ea2..de85f73 100644 --- a/src/pxl8_sdl3.c +++ b/src/pxl8_sdl3.c @@ -4,37 +4,27 @@ #include #include -#include "pxl8_atlas.h" #include "pxl8_color.h" #include "pxl8_log.h" -#include "pxl8_rec.h" #include "pxl8_sys.h" typedef struct pxl8_sdl3_context { - SDL_Texture* atlas_texture; - SDL_Texture* framebuffer_texture; + SDL_Texture* framebuffer; SDL_Renderer* renderer; SDL_Window* window; u32* rgba_buffer; size_t rgba_buffer_size; - - u32 atlas_width; - u32 atlas_height; } pxl8_sdl3_context; -static void* sdl3_create(pxl8_pixel_mode mode, pxl8_resolution resolution, +static void* sdl3_create(i32 render_w, i32 render_h, const char* title, i32 win_w, i32 win_h) { - (void)mode; - pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)SDL_calloc(1, sizeof(pxl8_sdl3_context)); if (!ctx) { pxl8_error("Failed to allocate SDL3 context"); return NULL; } - pxl8_size fb_size = pxl8_get_resolution_dimensions(resolution); - ctx->window = SDL_CreateWindow(title, win_w, win_h, SDL_WINDOW_RESIZABLE); if (!ctx->window) { pxl8_error("Failed to create window: %s", SDL_GetError()); @@ -54,11 +44,11 @@ static void* sdl3_create(pxl8_pixel_mode mode, pxl8_resolution resolution, pxl8_error("Failed to set vsync: %s", SDL_GetError()); } - ctx->framebuffer_texture = SDL_CreateTexture(ctx->renderer, + ctx->framebuffer = SDL_CreateTexture(ctx->renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, - fb_size.w, fb_size.h); - if (!ctx->framebuffer_texture) { + render_w, render_h); + if (!ctx->framebuffer) { pxl8_error("Failed to create framebuffer texture: %s", SDL_GetError()); SDL_DestroyRenderer(ctx->renderer); SDL_DestroyWindow(ctx->window); @@ -66,7 +56,7 @@ static void* sdl3_create(pxl8_pixel_mode mode, pxl8_resolution resolution, return NULL; } - SDL_SetTextureScaleMode(ctx->framebuffer_texture, SDL_SCALEMODE_NEAREST); + SDL_SetTextureScaleMode(ctx->framebuffer, SDL_SCALEMODE_NEAREST); ctx->rgba_buffer = NULL; ctx->rgba_buffer_size = 0; @@ -79,21 +69,10 @@ static void sdl3_destroy(void* platform_data) { pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data; - if (ctx->rgba_buffer) { - SDL_free(ctx->rgba_buffer); - } - if (ctx->atlas_texture) { - SDL_DestroyTexture(ctx->atlas_texture); - } - if (ctx->framebuffer_texture) { - SDL_DestroyTexture(ctx->framebuffer_texture); - } - if (ctx->renderer) { - SDL_DestroyRenderer(ctx->renderer); - } - if (ctx->window) { - SDL_DestroyWindow(ctx->window); - } + if (ctx->rgba_buffer) SDL_free(ctx->rgba_buffer); + if (ctx->framebuffer) SDL_DestroyTexture(ctx->framebuffer); + if (ctx->renderer) SDL_DestroyRenderer(ctx->renderer); + if (ctx->window) SDL_DestroyWindow(ctx->window); SDL_free(ctx); } @@ -111,17 +90,16 @@ static void sdl3_present(void* platform_data) { SDL_SetRenderDrawColor(ctx->renderer, 0, 0, 0, 255); SDL_RenderClear(ctx->renderer); - if (ctx->framebuffer_texture) { - SDL_RenderTexture(ctx->renderer, ctx->framebuffer_texture, NULL, NULL); + if (ctx->framebuffer) { + SDL_RenderTexture(ctx->renderer, ctx->framebuffer, NULL, NULL); } SDL_RenderPresent(ctx->renderer); } -static void sdl3_upload_framebuffer(void* platform_data, const u8* fb, - i32 w, i32 h, const u32* palette, - pxl8_pixel_mode mode) { - if (!platform_data || !fb) return; +static void sdl3_upload_texture(void* platform_data, const u8* pixels, u32 w, u32 h, + const u32* palette, u32 bpp) { + if (!platform_data || !pixels) return; pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data; size_t needed_size = w * h; @@ -133,81 +111,18 @@ static void sdl3_upload_framebuffer(void* platform_data, const u8* fb, ctx->rgba_buffer_size = needed_size; } - if (mode == PXL8_PIXEL_HICOLOR) { - const u16* fb16 = (const u16*)fb; - for (i32 i = 0; i < w * h; i++) { - ctx->rgba_buffer[i] = pxl8_rgb565_to_rgba32(fb16[i]); + if (bpp == 2) { + const u16* pixels16 = (const u16*)pixels; + for (u32 i = 0; i < w * h; i++) { + ctx->rgba_buffer[i] = pxl8_rgb565_to_rgba32(pixels16[i]); } } else { - for (i32 i = 0; i < w * h; i++) { - u8 index = fb[i]; - ctx->rgba_buffer[i] = palette[index]; + for (u32 i = 0; i < w * h; i++) { + ctx->rgba_buffer[i] = palette[pixels[i]]; } } - SDL_UpdateTexture(ctx->framebuffer_texture, NULL, ctx->rgba_buffer, w * 4); -} - -static void sdl3_upload_atlas(void* platform_data, const pxl8_atlas* atlas, - const u32* palette, pxl8_pixel_mode mode) { - if (!platform_data || !atlas) return; - - pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data; - - if (!pxl8_atlas_is_dirty(atlas)) return; - - u32 atlas_w = pxl8_atlas_get_width(atlas); - u32 atlas_h = pxl8_atlas_get_height(atlas); - const u8* atlas_pixels = pxl8_atlas_get_pixels(atlas); - - if (!ctx->atlas_texture || ctx->atlas_width != atlas_w || ctx->atlas_height != atlas_h) { - if (ctx->atlas_texture) { - SDL_DestroyTexture(ctx->atlas_texture); - } - - ctx->atlas_texture = SDL_CreateTexture( - ctx->renderer, - SDL_PIXELFORMAT_RGBA32, - SDL_TEXTUREACCESS_STREAMING, - atlas_w, - atlas_h - ); - - if (!ctx->atlas_texture) { - pxl8_error("Failed to create atlas texture: %s", SDL_GetError()); - return; - } - - SDL_SetTextureScaleMode(ctx->atlas_texture, SDL_SCALEMODE_NEAREST); - SDL_SetTextureBlendMode(ctx->atlas_texture, SDL_BLENDMODE_BLEND); - - ctx->atlas_width = atlas_w; - ctx->atlas_height = atlas_h; - } - - size_t needed_size = atlas_w * atlas_h; - - if (ctx->rgba_buffer_size < needed_size) { - u32* new_buffer = (u32*)SDL_realloc(ctx->rgba_buffer, needed_size * 4); - if (!new_buffer) return; - ctx->rgba_buffer = new_buffer; - ctx->rgba_buffer_size = needed_size; - } - - if (mode == PXL8_PIXEL_HICOLOR) { - const u16* atlas16 = (const u16*)atlas_pixels; - for (u32 i = 0; i < atlas_w * atlas_h; i++) { - u16 pixel = atlas16[i]; - ctx->rgba_buffer[i] = (pixel != 0) ? pxl8_rgb565_to_rgba32(pixel) : 0x00000000; - } - } else { - for (u32 i = 0; i < atlas_w * atlas_h; i++) { - u8 index = atlas_pixels[i]; - ctx->rgba_buffer[i] = palette[index]; - } - } - - SDL_UpdateTexture(ctx->atlas_texture, NULL, ctx->rgba_buffer, atlas_w * 4); + SDL_UpdateTexture(ctx->framebuffer, NULL, ctx->rgba_buffer, w * 4); } SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { @@ -371,7 +286,7 @@ static void sdl3_set_relative_mouse_mode(void* platform_data, bool enabled) { } } -static void sdl3_set_cursor(void* platform_data, pxl8_cursor cursor) { +static void sdl3_set_cursor(void* platform_data, u32 cursor) { if (!platform_data) return; SDL_SystemCursor sdl_cursor; @@ -410,7 +325,6 @@ const pxl8_hal pxl8_hal_sdl3 = { .center_cursor = sdl3_center_cursor, .present = sdl3_present, .set_cursor = sdl3_set_cursor, - .upload_atlas = sdl3_upload_atlas, - .upload_framebuffer = sdl3_upload_framebuffer, + .upload_texture = sdl3_upload_texture, .set_relative_mouse_mode = sdl3_set_relative_mouse_mode, };