pxl8/src/pxl8_script.c

575 lines
20 KiB
C
Raw Normal View History

2025-10-04 04:13:48 -05:00
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
#include <SDL3/SDL.h>
#include "pxl8_macros.h"
#include "pxl8_script.h"
2025-10-04 11:55:04 -05:00
#include "pxl8_ui.h"
2025-10-04 04:13:48 -05:00
#include "pxl8_vfx.h"
struct pxl8_script {
lua_State* L;
pxl8_gfx* gfx;
pxl8_input_state* input;
2025-10-04 11:55:04 -05:00
pxl8_ui* ui;
2025-10-04 04:13:48 -05:00
char last_error[1024];
char main_path[256];
char watch_dir[256];
time_t latest_mod_time;
};
static const char* pxl8_ffi_cdefs =
"typedef uint8_t u8;\n"
"typedef uint16_t u16;\n"
"typedef uint32_t u32;\n"
"typedef uint64_t u64;\n"
"typedef int8_t i8;\n"
"typedef int16_t i16;\n"
"typedef int32_t i32;\n"
"typedef int64_t i64;\n"
"typedef float f32;\n"
"typedef double f64;\n"
"typedef struct pxl8_gfx pxl8_gfx;\n"
2025-10-04 11:55:04 -05:00
"typedef struct { int x, y, w, h; } pxl8_bounds;\n"
2025-10-04 04:13:48 -05:00
"typedef struct { int x, y; } pxl8_point;\n"
"\n"
"i32 pxl8_gfx_get_height(pxl8_gfx* ctx);\n"
"i32 pxl8_gfx_get_width(pxl8_gfx* ctx);\n"
"void pxl8_circle(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n"
"void pxl8_circle_fill(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n"
"void pxl8_clr(pxl8_gfx* ctx, u32 color);\n"
"u32 pxl8_get_pixel(pxl8_gfx* ctx, i32 x, i32 y);\n"
"void pxl8_line(pxl8_gfx* ctx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color);\n"
"void pxl8_pixel(pxl8_gfx* ctx, i32 x, i32 y, u32 color);\n"
"void pxl8_rect(pxl8_gfx* ctx, i32 x, i32 y, i32 w, i32 h, u32 color);\n"
"void pxl8_rect_fill(pxl8_gfx* ctx, i32 x, i32 y, i32 w, i32 h, u32 color);\n"
"void pxl8_sprite(pxl8_gfx* ctx, i32 id, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y);\n"
"void pxl8_text(pxl8_gfx* ctx, const char* str, i32 x, i32 y, u32 color);\n"
"void pxl8_gfx_color_ramp(pxl8_gfx* ctx, u8 start, u8 count, u32 from_color, u32 to_color);\n"
"void pxl8_gfx_fade_palette(pxl8_gfx* ctx, u8 start, u8 count, f32 amount, u32 target_color);\n"
"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"
2025-10-05 16:25:17 -05:00
"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"
2025-10-04 04:13:48 -05:00
"typedef struct pxl8_input_state pxl8_input_state;\n"
"bool pxl8_key_down(const pxl8_input_state* input, i32 key);\n"
"bool pxl8_key_pressed(const pxl8_input_state* input, i32 key);\n"
"void pxl8_lua_debug(const char* msg);\n"
"void pxl8_lua_error(const char* msg);\n"
"void pxl8_lua_info(const char* msg);\n"
"void pxl8_lua_trace(const char* msg);\n"
"void pxl8_lua_warn(const char* msg);\n"
"\n"
"typedef struct pxl8_tilemap pxl8_tilemap;\n"
"typedef struct pxl8_tilesheet pxl8_tilesheet;\n"
"pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size);\n"
"void pxl8_tilemap_destroy(pxl8_tilemap* tilemap);\n"
"bool pxl8_tilemap_check_collision(const pxl8_tilemap* tilemap, i32 x, i32 y, i32 w, i32 h);\n"
"u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);\n"
"bool pxl8_tilemap_is_solid(const pxl8_tilemap* tilemap, u32 x, u32 y);\n"
"void pxl8_tilemap_render(const pxl8_tilemap* tilemap, pxl8_gfx* gfx);\n"
"void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx* gfx, u32 layer);\n"
"void pxl8_tilemap_set_camera(pxl8_tilemap* tilemap, i32 x, i32 y);\n"
"void pxl8_tilemap_set_tile(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u16 tile_id, u8 flags);\n"
"i32 pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* tilesheet);\n"
"pxl8_tilesheet* pxl8_tilesheet_create(u32 tile_size);\n"
"void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet);\n"
"i32 pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx* gfx);\n"
"\n"
"typedef struct {\n"
" float angle;\n"
" float ax, ay, az;\n"
" unsigned int color;\n"
" unsigned int end_color;\n"
" unsigned char flags;\n"
" float life;\n"
" float max_life;\n"
" float size;\n"
" float spin;\n"
" unsigned int start_color;\n"
" float vx, vy, vz;\n"
" float x, y, z;\n"
"} pxl8_particle;\n"
"\n"
"typedef struct {\n"
" float amplitude;\n"
" float base_y;\n"
" unsigned int color;\n"
" unsigned int fade_color;\n"
" int height;\n"
" float phase;\n"
" float speed;\n"
"} pxl8_raster_bar;\n"
"\n"
"typedef struct pxl8_particles pxl8_particles;\n"
"pxl8_particles* pxl8_particles_create(u32 max_count);\n"
"void pxl8_particles_destroy(pxl8_particles* particles);\n"
"void pxl8_particles_clear(pxl8_particles* particles);\n"
"void pxl8_particles_emit(pxl8_particles* particles, u32 count);\n"
"void pxl8_particles_render(pxl8_particles* particles, pxl8_gfx* gfx);\n"
"void pxl8_particles_update(pxl8_particles* particles, float dt);\n"
"void pxl8_vfx_explosion(pxl8_particles* particles, int x, int y, unsigned int color, float force);\n"
"void pxl8_vfx_fire(pxl8_particles* particles, int x, int y, int width, unsigned char palette_start);\n"
"void pxl8_vfx_plasma(pxl8_gfx* ctx, f32 time, f32 scale1, f32 scale2, u8 palette_offset);\n"
"void pxl8_vfx_rain(pxl8_particles* particles, int width, float wind);\n"
"void pxl8_vfx_raster_bars(pxl8_gfx* ctx, pxl8_raster_bar* bars, u32 bar_count, f32 time);\n"
"void pxl8_vfx_rotozoom(pxl8_gfx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy);\n"
"void pxl8_vfx_smoke(pxl8_particles* particles, int x, int y, unsigned char color);\n"
"void pxl8_vfx_snow(pxl8_particles* particles, int width, float wind);\n"
"void pxl8_vfx_sparks(pxl8_particles* particles, int x, int y, unsigned int color);\n"
"void pxl8_vfx_starfield(pxl8_particles* particles, float speed, float spread);\n"
"void pxl8_vfx_tunnel(pxl8_gfx* ctx, f32 time, f32 speed, f32 twist);\n"
"void pxl8_vfx_water_ripple(pxl8_gfx* ctx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping);\n"
"\n"
"typedef struct { float x, y, z; } pxl8_vec3;\n"
"typedef struct { float x, y, z, w; } pxl8_vec4;\n"
"typedef struct { float m[16]; } pxl8_mat4;\n"
"\n"
"void pxl8_3d_clear_zbuffer(pxl8_gfx* gfx);\n"
"void pxl8_3d_draw_line_3d(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u32 color);\n"
"void pxl8_3d_draw_triangle_raw(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, u32 color);\n"
2025-10-05 16:25:17 -05:00
"void pxl8_3d_draw_triangle_textured(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, f32 u0, f32 v0, f32 u1, f32 v1, f32 u2, f32 v2, u32 texture_id);\n"
"void pxl8_3d_set_affine_textures(pxl8_gfx* gfx, bool affine);\n"
2025-10-04 04:13:48 -05:00
"void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling);\n"
"void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat);\n"
"void pxl8_3d_set_projection(pxl8_gfx* gfx, pxl8_mat4 mat);\n"
"void pxl8_3d_set_view(pxl8_gfx* gfx, pxl8_mat4 mat);\n"
"void pxl8_3d_set_wireframe(pxl8_gfx* gfx, bool wireframe);\n"
"pxl8_mat4 pxl8_mat4_identity(void);\n"
"pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);\n"
"pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b);\n"
"pxl8_mat4 pxl8_mat4_ortho(float left, float right, float bottom, float top, float near, float far);\n"
"pxl8_mat4 pxl8_mat4_perspective(float fov, float aspect, float near, float far);\n"
"pxl8_mat4 pxl8_mat4_rotate_x(float angle);\n"
"pxl8_mat4 pxl8_mat4_rotate_y(float angle);\n"
"pxl8_mat4 pxl8_mat4_rotate_z(float angle);\n"
"pxl8_mat4 pxl8_mat4_scale(float x, float y, float z);\n"
2025-10-04 11:55:04 -05:00
"pxl8_mat4 pxl8_mat4_translate(float x, float y, float z);\n"
"\n"
"typedef struct pxl8_ui pxl8_ui;\n"
"typedef struct { unsigned char bg_color; unsigned int sprite_id; int corner_size; int edge_size; int padding; } pxl8_frame_theme;\n"
"typedef struct { bool enabled; const char* label; } pxl8_menu_item;\n"
"pxl8_ui* pxl8_ui_create(pxl8_gfx* gfx);\n"
"void pxl8_ui_destroy(pxl8_ui* ui);\n"
"void pxl8_ui_frame_begin(pxl8_ui* ui);\n"
"void pxl8_ui_frame_end(pxl8_ui* ui);\n"
"void pxl8_ui_input_keydown(pxl8_ui* ui, int key);\n"
"void pxl8_ui_input_keyup(pxl8_ui* ui, int key);\n"
"void pxl8_ui_input_mousedown(pxl8_ui* ui, int x, int y, int button);\n"
"void pxl8_ui_input_mousemove(pxl8_ui* ui, int x, int y);\n"
"void pxl8_ui_input_mouseup(pxl8_ui* ui, int x, int y, int button);\n"
"void pxl8_ui_input_scroll(pxl8_ui* ui, int x, int y);\n"
"void pxl8_ui_input_text(pxl8_ui* ui, const char* text);\n"
"bool pxl8_ui_button(pxl8_ui* ui, const char* label);\n"
"void pxl8_ui_label(pxl8_ui* ui, const char* text);\n"
"void pxl8_ui_layout_row(pxl8_ui* ui, int item_count, const int* widths, int height);\n"
"int pxl8_ui_menu(pxl8_ui* ui, pxl8_menu_item* items, int item_count);\n"
"void pxl8_ui_panel(pxl8_ui* ui, pxl8_bounds rect, pxl8_frame_theme* theme);\n"
"bool pxl8_ui_window_begin(pxl8_ui* ui, const char* title, pxl8_bounds rect, int options);\n"
"void pxl8_ui_window_end(pxl8_ui* ui);\n"
"pxl8_frame_theme pxl8_ui_theme_default(void);\n";
2025-10-04 04:13:48 -05:00
void pxl8_lua_info(const char* msg) {
pxl8_info("%s", msg);
}
void pxl8_lua_warn(const char* msg) {
pxl8_warn("%s", msg);
}
void pxl8_lua_error(const char* msg) {
pxl8_error("%s", msg);
}
void pxl8_lua_debug(const char* msg) {
pxl8_debug("%s", msg);
}
void pxl8_lua_trace(const char* msg) {
pxl8_trace("%s", msg);
}
static void pxl8_script_set_error(pxl8_script* script, const char* error) {
if (!script) return;
snprintf(script->last_error, sizeof(script->last_error), "%s", error ? error : "Unknown error");
}
const char* pxl8_script_get_last_error(pxl8_script* script) {
return script ? script->last_error : "Invalid script context";
}
pxl8_script* pxl8_script_create(void) {
pxl8_script* script = SDL_calloc(1, sizeof(pxl8_script));
if (!script) return NULL;
script->L = luaL_newstate();
if (!script->L) {
SDL_free(script);
return NULL;
}
luaL_openlibs(script->L);
lua_getglobal(script->L, "require");
lua_pushstring(script->L, "ffi");
if (lua_pcall(script->L, 1, 1, 0) != 0) {
pxl8_script_set_error(script, lua_tostring(script->L, -1));
pxl8_script_destroy(script);
return NULL;
}
lua_getfield(script->L, -1, "cdef");
lua_pushstring(script->L, pxl8_ffi_cdefs);
if (lua_pcall(script->L, 1, 0, 0) != 0) {
pxl8_script_set_error(script, lua_tostring(script->L, -1));
pxl8_script_destroy(script);
return NULL;
}
lua_pop(script->L, 1);
lua_getglobal(script->L, "package");
lua_getfield(script->L, -1, "path");
const char* current_path = lua_tostring(script->L, -1);
lua_pop(script->L, 1);
lua_pushfstring(script->L, "%s;src/lua/?.lua", current_path);
lua_setfield(script->L, -2, "path");
lua_pop(script->L, 1);
if (luaL_dofile(script->L, "lib/fennel/fennel.lua") == 0) {
lua_setglobal(script->L, "fennel");
}
script->last_error[0] = '\0';
return script;
}
void pxl8_script_destroy(pxl8_script* script) {
if (!script) return;
if (script->L) {
lua_close(script->L);
}
SDL_free(script);
}
void pxl8_script_set_gfx(pxl8_script* script, pxl8_gfx* gfx) {
if (!script) return;
script->gfx = gfx;
if (script->L && gfx) {
lua_pushlightuserdata(script->L, gfx);
lua_setglobal(script->L, "_pxl8_gfx");
}
}
void pxl8_script_set_input(pxl8_script* script, pxl8_input_state* input) {
if (!script) return;
script->input = input;
if (script->L && input) {
lua_pushlightuserdata(script->L, input);
lua_setglobal(script->L, "_pxl8_input");
}
}
2025-10-04 11:55:04 -05:00
void pxl8_script_set_ui(pxl8_script* script, pxl8_ui* ui) {
if (!script) return;
script->ui = ui;
if (script->L && ui) {
lua_pushlightuserdata(script->L, ui);
lua_setglobal(script->L, "_pxl8_ui");
}
}
2025-10-04 04:13:48 -05:00
2025-10-04 11:55:04 -05:00
static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* filename, char** out_basename) {
2025-10-04 04:13:48 -05:00
char* filename_copy = strdup(filename);
char* last_slash = strrchr(filename_copy, '/');
2025-10-04 11:55:04 -05:00
*out_basename = (char*)filename;
2025-10-04 04:13:48 -05:00
if (last_slash) {
*last_slash = '\0';
char* script_dir = realpath(filename_copy, NULL);
char* original_cwd = getcwd(NULL, 0);
if (script_dir && original_cwd) {
chdir(script_dir);
pxl8_script_set_cart_path(script, script_dir, original_cwd);
2025-10-04 11:55:04 -05:00
*out_basename = strdup(last_slash + 1);
2025-10-04 04:13:48 -05:00
}
free(script_dir);
free(original_cwd);
}
2025-10-04 11:55:04 -05:00
free(filename_copy);
return PXL8_OK;
}
pxl8_result pxl8_script_run_file(pxl8_script* script, const char* filename) {
if (!script || !script->L || !filename) return PXL8_ERROR_NULL_POINTER;
char* basename;
pxl8_script_prepare_path(script, filename, &basename);
2025-10-04 04:13:48 -05:00
pxl8_result result = PXL8_OK;
if (luaL_dofile(script->L, basename) != 0) {
pxl8_script_set_error(script, lua_tostring(script->L, -1));
lua_pop(script->L, 1);
result = PXL8_ERROR_SCRIPT_ERROR;
} else {
script->last_error[0] = '\0';
}
2025-10-04 11:55:04 -05:00
if (basename != filename) free(basename);
2025-10-04 04:13:48 -05:00
return result;
}
void pxl8_script_set_cart_path(pxl8_script* script, const char* cart_path, const char* original_cwd) {
if (!script || !script->L || !cart_path || !original_cwd) return;
lua_getglobal(script->L, "package");
lua_getfield(script->L, -1, "path");
const char* current_path = lua_tostring(script->L, -1);
char new_path[2048];
snprintf(new_path, sizeof(new_path),
"%s/?.lua;%s/?/init.lua;%s/src/?.lua;%s/src/?/init.lua;%s/src/lua/?.lua;%s",
cart_path, cart_path, cart_path, cart_path, original_cwd,
current_path ? current_path : "");
lua_pushstring(script->L, new_path);
lua_setfield(script->L, -3, "path");
lua_pop(script->L, 2);
lua_getglobal(script->L, "fennel");
if (!lua_isnil(script->L, -1)) {
lua_getfield(script->L, -1, "path");
const char* fennel_path = lua_tostring(script->L, -1);
snprintf(new_path, sizeof(new_path),
"%s/?.fnl;%s/?/init.fnl;%s/src/?.fnl;%s/src/?/init.fnl;%s",
cart_path, cart_path, cart_path, cart_path,
fennel_path ? fennel_path : "");
lua_pushstring(script->L, new_path);
lua_setfield(script->L, -3, "path");
lua_pop(script->L, 1);
}
lua_pop(script->L, 1);
}
pxl8_result pxl8_script_run_fennel_file(pxl8_script* script, const char* filename) {
if (!script || !script->L || !filename) return PXL8_ERROR_NULL_POINTER;
2025-10-04 11:55:04 -05:00
char* basename;
pxl8_script_prepare_path(script, filename, &basename);
2025-10-04 04:13:48 -05:00
lua_getglobal(script->L, "fennel");
if (lua_isnil(script->L, -1)) {
pxl8_script_set_error(script, "Fennel not loaded");
lua_pop(script->L, 1);
2025-10-04 11:55:04 -05:00
if (basename != filename) free(basename);
2025-10-04 04:13:48 -05:00
return PXL8_ERROR_SCRIPT_ERROR;
}
lua_getfield(script->L, -1, "dofile");
lua_pushstring(script->L, basename);
pxl8_result result = PXL8_OK;
if (lua_pcall(script->L, 1, 0, 0) != 0) {
pxl8_script_set_error(script, lua_tostring(script->L, -1));
lua_pop(script->L, 1);
result = PXL8_ERROR_SCRIPT_ERROR;
} else {
script->last_error[0] = '\0';
}
lua_pop(script->L, 1);
2025-10-04 11:55:04 -05:00
if (basename != filename) free(basename);
2025-10-04 04:13:48 -05:00
return result;
}
pxl8_result pxl8_script_eval(pxl8_script* script, const char* code) {
if (!script || !script->L || !code) return PXL8_ERROR_NULL_POINTER;
lua_getglobal(script->L, "fennel");
if (lua_isnil(script->L, -1)) {
pxl8_script_set_error(script, "Fennel not loaded");
lua_pop(script->L, 1);
return PXL8_ERROR_SCRIPT_ERROR;
}
lua_getfield(script->L, -1, "eval");
lua_pushstring(script->L, code);
if (lua_pcall(script->L, 1, 1, 0) != 0) {
pxl8_script_set_error(script, lua_tostring(script->L, -1));
lua_remove(script->L, -2);
return PXL8_ERROR_SCRIPT_ERROR;
}
lua_remove(script->L, -2);
script->last_error[0] = '\0';
return PXL8_OK;
}
pxl8_result pxl8_script_load_module(pxl8_script* script, const char* module_name) {
if (!script || !script->L || !module_name) return PXL8_ERROR_NULL_POINTER;
lua_getglobal(script->L, "require");
lua_pushstring(script->L, module_name);
if (lua_pcall(script->L, 1, 1, 0) != 0) {
pxl8_script_set_error(script, lua_tostring(script->L, -1));
lua_pop(script->L, 1);
return PXL8_ERROR_SCRIPT_ERROR;
}
lua_setglobal(script->L, module_name);
script->last_error[0] = '\0';
return PXL8_OK;
}
pxl8_result pxl8_script_call_function(pxl8_script* script, const char* name) {
if (!script || !script->L || !name) return PXL8_ERROR_NULL_POINTER;
lua_getglobal(script->L, name);
if (!lua_isfunction(script->L, -1)) {
lua_pop(script->L, 1);
return PXL8_OK;
}
if (lua_pcall(script->L, 0, 0, 0) != 0) {
pxl8_script_set_error(script, lua_tostring(script->L, -1));
lua_pop(script->L, 1);
return PXL8_ERROR_SCRIPT_ERROR;
}
script->last_error[0] = '\0';
return PXL8_OK;
}
pxl8_result pxl8_script_call_function_f32(pxl8_script* script, const char* name, f32 arg) {
if (!script || !script->L || !name) return PXL8_ERROR_NULL_POINTER;
lua_getglobal(script->L, name);
if (!lua_isfunction(script->L, -1)) {
lua_pop(script->L, 1);
return PXL8_OK;
}
lua_pushnumber(script->L, arg);
if (lua_pcall(script->L, 1, 0, 0) != 0) {
pxl8_script_set_error(script, lua_tostring(script->L, -1));
lua_pop(script->L, 1);
return PXL8_ERROR_SCRIPT_ERROR;
}
script->last_error[0] = '\0';
return PXL8_OK;
}
static time_t get_file_mod_time(const char* path) {
struct stat file_stat;
if (stat(path, &file_stat) == 0) {
return file_stat.st_mtime;
}
return 0;
}
static time_t get_latest_script_mod_time(const char* dir_path) {
DIR* dir = opendir(dir_path);
if (!dir) return 0;
time_t latest = 0;
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.') continue;
size_t len = strlen(entry->d_name);
bool is_script = (len > 4 && strcmp(entry->d_name + len - 4, ".fnl") == 0) ||
(len > 4 && strcmp(entry->d_name + len - 4, ".lua") == 0);
if (is_script) {
char full_path[512];
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
time_t mod_time = get_file_mod_time(full_path);
if (mod_time > latest) {
latest = mod_time;
}
}
}
closedir(dir);
return latest;
}
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';
char* last_slash = strrchr(script->main_path, '/');
if (last_slash) {
size_t dir_len = last_slash - script->main_path;
strncpy(script->watch_dir, script->main_path, dir_len);
script->watch_dir[dir_len] = '\0';
} else {
strcpy(script->watch_dir, ".");
}
script->latest_mod_time = get_latest_script_mod_time(script->watch_dir);
const char* ext = strrchr(path, '.');
pxl8_result result = PXL8_ERROR_INVALID_FORMAT;
if (ext && strcmp(ext, ".fnl") == 0) {
result = pxl8_script_run_fennel_file(script, path);
} else if (ext && strcmp(ext, ".lua") == 0) {
result = pxl8_script_run_file(script, path);
} else {
pxl8_script_set_error(script, "Unknown script type (expected .fnl or .lua)");
return PXL8_ERROR_INVALID_FORMAT;
}
if (result == PXL8_OK) {
pxl8_info("Loaded script: %s", path);
pxl8_script_call_function(script, "init");
} else {
pxl8_warn("Failed to load script: %s", script->last_error);
}
return result;
}
bool pxl8_script_check_reload(pxl8_script* script) {
if (!script || script->main_path[0] == '\0') return false;
time_t current_mod_time = get_latest_script_mod_time(script->watch_dir);
if (current_mod_time > script->latest_mod_time && current_mod_time != 0) {
pxl8_info("Script files modified, reloading: %s", script->main_path);
script->latest_mod_time = current_mod_time;
const char* ext = strrchr(script->main_path, '.');
if (ext && strcmp(ext, ".fnl") == 0) {
if (pxl8_script_run_fennel_file(script, script->main_path) == PXL8_OK) {
pxl8_script_call_function(script, "init");
return true;
}
} else if (ext && strcmp(ext, ".lua") == 0) {
if (pxl8_script_run_file(script, script->main_path) == PXL8_OK) {
pxl8_script_call_function(script, "init");
return true;
}
}
}
return false;
}