2025-11-13 07:15:41 -06:00
|
|
|
#include "pxl8_script.h"
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
#include <limits.h>
|
2025-10-04 04:13:48 -05:00
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
2025-11-13 07:15:41 -06:00
|
|
|
|
|
|
|
|
#include <dirent.h>
|
2025-10-04 04:13:48 -05:00
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
#include <lauxlib.h>
|
|
|
|
|
#include <lua.h>
|
|
|
|
|
#include <lualib.h>
|
|
|
|
|
|
|
|
|
|
#include "pxl8_macros.h"
|
2025-11-21 11:51:23 -06:00
|
|
|
#include "pxl8_gui.h"
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
struct pxl8_script {
|
|
|
|
|
lua_State* L;
|
|
|
|
|
pxl8_gfx* gfx;
|
|
|
|
|
pxl8_input_state* input;
|
2025-11-10 09:59:42 -06:00
|
|
|
char last_error[2048];
|
2025-10-04 04:13:48 -05:00
|
|
|
char main_path[256];
|
|
|
|
|
char watch_dir[256];
|
|
|
|
|
time_t latest_mod_time;
|
|
|
|
|
};
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
#define PXL8_MAX_REPL_COMMAND_SIZE 4096
|
|
|
|
|
|
|
|
|
|
static void pxl8_script_repl_promote_locals(const char* input, char* output, size_t output_size) {
|
|
|
|
|
size_t i = 0;
|
|
|
|
|
size_t j = 0;
|
|
|
|
|
bool in_string = false;
|
|
|
|
|
bool in_comment = false;
|
|
|
|
|
|
|
|
|
|
while (input[i] && j < output_size - 1) {
|
|
|
|
|
if (in_comment) {
|
|
|
|
|
output[j++] = input[i];
|
|
|
|
|
if (input[i] == '\n') {
|
|
|
|
|
in_comment = false;
|
|
|
|
|
}
|
|
|
|
|
i++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (input[i] == ';' && !in_string) {
|
|
|
|
|
in_comment = true;
|
|
|
|
|
output[j++] = input[i++];
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (input[i] == '"' && (i == 0 || input[i-1] != '\\')) {
|
|
|
|
|
in_string = !in_string;
|
|
|
|
|
output[j++] = input[i++];
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!in_string && input[i] == '(' &&
|
|
|
|
|
strncmp(&input[i], "(local ", 7) == 0) {
|
2025-11-10 09:59:42 -06:00
|
|
|
memcpy(&output[j], "(global ", 8);
|
2025-11-01 12:39:59 -05:00
|
|
|
j += 8;
|
|
|
|
|
i += 7;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
output[j++] = input[i++];
|
|
|
|
|
}
|
|
|
|
|
output[j] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
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"
|
2025-11-18 23:50:02 -06:00
|
|
|
"typedef struct pxl8 pxl8;\n"
|
2025-10-04 04:13:48 -05:00
|
|
|
"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"
|
2025-11-18 23:50:02 -06:00
|
|
|
"f32 pxl8_get_fps(const pxl8* sys);\n"
|
2025-11-11 23:26:51 -06:00
|
|
|
"\n"
|
2025-10-04 04:13:48 -05:00
|
|
|
"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"
|
2025-11-15 11:55:00 -06:00
|
|
|
"void pxl8_clear(pxl8_gfx* ctx, u32 color);\n"
|
2025-10-04 04:13:48 -05:00
|
|
|
"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"
|
2025-10-06 18:14:07 -05:00
|
|
|
"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"
|
|
|
|
|
"bool pxl8_key_released(const pxl8_input_state* input, const char* key_name);\n"
|
2025-11-21 11:51:23 -06:00
|
|
|
"bool pxl8_mouse_pressed(const pxl8_input_state* input, i32 button);\n"
|
|
|
|
|
"bool pxl8_mouse_released(const pxl8_input_state* input, i32 button);\n"
|
2025-10-06 18:14:07 -05:00
|
|
|
"int pxl8_mouse_wheel_x(const pxl8_input_state* input);\n"
|
|
|
|
|
"int pxl8_mouse_wheel_y(const pxl8_input_state* input);\n"
|
|
|
|
|
"int pxl8_mouse_x(const pxl8_input_state* input);\n"
|
|
|
|
|
"int pxl8_mouse_y(const pxl8_input_state* input);\n"
|
2025-11-19 22:18:08 -06:00
|
|
|
"int pxl8_mouse_dx(const pxl8_input_state* input);\n"
|
|
|
|
|
"int pxl8_mouse_dy(const pxl8_input_state* input);\n"
|
2025-11-21 11:51:23 -06:00
|
|
|
"typedef enum { PXL8_CURSOR_ARROW = 0, PXL8_CURSOR_HAND = 1 } pxl8_cursor;\n"
|
|
|
|
|
"void pxl8_center_cursor(pxl8* sys);\n"
|
|
|
|
|
"void pxl8_set_cursor(pxl8* sys, pxl8_cursor cursor);\n"
|
2025-11-19 22:18:08 -06:00
|
|
|
"void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled);\n"
|
2025-11-21 11:51:23 -06:00
|
|
|
"void pxl8_set_running(pxl8* sys, bool running);\n"
|
2025-10-04 04:13:48 -05:00
|
|
|
"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"
|
2025-11-01 12:39:59 -05:00
|
|
|
"typedef u32 pxl8_tile;\n"
|
2025-10-04 04:13:48 -05:00
|
|
|
"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"
|
2025-11-01 12:39:59 -05:00
|
|
|
"u32 pxl8_tilemap_get_height(const pxl8_tilemap* tilemap);\n"
|
|
|
|
|
"pxl8_tile pxl8_tilemap_get_tile(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);\n"
|
|
|
|
|
"u32 pxl8_tilemap_get_tile_size(const pxl8_tilemap* tilemap);\n"
|
|
|
|
|
"void* pxl8_tilemap_get_tile_user_data(const pxl8_tilemap* tilemap, u16 tile_id);\n"
|
|
|
|
|
"u32 pxl8_tilemap_get_width(const pxl8_tilemap* tilemap);\n"
|
|
|
|
|
"void pxl8_tilemap_set_tile_user_data(pxl8_tilemap* tilemap, u16 tile_id, void* user_data);\n"
|
2025-10-04 04:13:48 -05:00
|
|
|
"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"
|
2025-11-01 12:39:59 -05:00
|
|
|
"i32 pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u32 layer);\n"
|
2025-10-04 04:13:48 -05:00
|
|
|
"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"
|
2025-11-15 11:40:27 -06:00
|
|
|
"typedef struct pxl8_transition pxl8_transition;\n"
|
|
|
|
|
"typedef struct pxl8_anim pxl8_anim;\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"pxl8_transition* pxl8_transition_create(i32 type, f32 duration);\n"
|
|
|
|
|
"void pxl8_transition_destroy(pxl8_transition* transition);\n"
|
|
|
|
|
"f32 pxl8_transition_get_progress(const pxl8_transition* transition);\n"
|
|
|
|
|
"bool pxl8_transition_is_active(const pxl8_transition* transition);\n"
|
|
|
|
|
"bool pxl8_transition_is_complete(const pxl8_transition* transition);\n"
|
|
|
|
|
"void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx);\n"
|
|
|
|
|
"void pxl8_transition_reset(pxl8_transition* transition);\n"
|
|
|
|
|
"void pxl8_transition_set_color(pxl8_transition* transition, u32 color);\n"
|
|
|
|
|
"void pxl8_transition_set_reverse(pxl8_transition* transition, bool reverse);\n"
|
|
|
|
|
"void pxl8_transition_start(pxl8_transition* transition);\n"
|
|
|
|
|
"void pxl8_transition_stop(pxl8_transition* transition);\n"
|
|
|
|
|
"void pxl8_transition_update(pxl8_transition* transition, f32 dt);\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"pxl8_anim* pxl8_anim_create(const u32* frame_ids, const u16* frame_durations, u16 frame_count);\n"
|
|
|
|
|
"pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path);\n"
|
|
|
|
|
"void pxl8_anim_destroy(pxl8_anim* anim);\n"
|
|
|
|
|
"i32 pxl8_anim_add_state(pxl8_anim* anim, const char* name, pxl8_anim* state_anim);\n"
|
|
|
|
|
"u16 pxl8_anim_get_current_frame(const pxl8_anim* anim);\n"
|
|
|
|
|
"u32 pxl8_anim_get_current_frame_id(const pxl8_anim* anim);\n"
|
|
|
|
|
"const char* pxl8_anim_get_state(const pxl8_anim* anim);\n"
|
|
|
|
|
"bool pxl8_anim_has_state_machine(const pxl8_anim* anim);\n"
|
|
|
|
|
"bool pxl8_anim_is_complete(const pxl8_anim* anim);\n"
|
|
|
|
|
"bool pxl8_anim_is_playing(const pxl8_anim* anim);\n"
|
|
|
|
|
"void pxl8_anim_pause(pxl8_anim* anim);\n"
|
|
|
|
|
"void pxl8_anim_play(pxl8_anim* anim);\n"
|
|
|
|
|
"void pxl8_anim_render_sprite(const pxl8_anim* anim, pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h);\n"
|
|
|
|
|
"void pxl8_anim_reset(pxl8_anim* anim);\n"
|
|
|
|
|
"void pxl8_anim_set_frame(pxl8_anim* anim, u16 frame);\n"
|
|
|
|
|
"void pxl8_anim_set_loop(pxl8_anim* anim, bool loop);\n"
|
|
|
|
|
"void pxl8_anim_set_reverse(pxl8_anim* anim, bool reverse);\n"
|
|
|
|
|
"void pxl8_anim_set_speed(pxl8_anim* anim, f32 speed);\n"
|
|
|
|
|
"i32 pxl8_anim_set_state(pxl8_anim* anim, const char* name);\n"
|
|
|
|
|
"void pxl8_anim_stop(pxl8_anim* anim);\n"
|
|
|
|
|
"void pxl8_anim_update(pxl8_anim* anim, f32 dt);\n"
|
|
|
|
|
"\n"
|
2025-10-04 04:13:48 -05:00
|
|
|
"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"
|
2025-11-09 06:30:17 -06:00
|
|
|
"typedef enum pxl8_procgen_type {\n"
|
2025-11-20 20:55:45 -06:00
|
|
|
" PXL8_PROCGEN_ROOMS = 0,\n"
|
|
|
|
|
" PXL8_PROCGEN_TERRAIN = 1\n"
|
2025-11-09 06:30:17 -06:00
|
|
|
"} pxl8_procgen_type;\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"typedef struct pxl8_procgen_params {\n"
|
|
|
|
|
" pxl8_procgen_type type;\n"
|
|
|
|
|
" int width;\n"
|
|
|
|
|
" int height;\n"
|
|
|
|
|
" int depth;\n"
|
|
|
|
|
" unsigned int seed;\n"
|
2025-11-20 20:55:45 -06:00
|
|
|
" int min_room_size;\n"
|
|
|
|
|
" int max_room_size;\n"
|
|
|
|
|
" int num_rooms;\n"
|
2025-11-09 06:30:17 -06:00
|
|
|
"} pxl8_procgen_params;\n"
|
|
|
|
|
"\n"
|
2025-11-11 21:24:53 -06:00
|
|
|
"typedef struct pxl8_procgen_tex_params {\n"
|
|
|
|
|
" char name[16];\n"
|
|
|
|
|
" unsigned int seed;\n"
|
|
|
|
|
" int width;\n"
|
|
|
|
|
" int height;\n"
|
|
|
|
|
" float scale;\n"
|
|
|
|
|
" float roughness;\n"
|
|
|
|
|
" unsigned char base_color;\n"
|
|
|
|
|
" unsigned char variation;\n"
|
|
|
|
|
"} pxl8_procgen_tex_params;\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params);\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"typedef struct pxl8_bsp pxl8_bsp;\n"
|
|
|
|
|
"typedef struct pxl8_bsp_face pxl8_bsp_face;\n"
|
|
|
|
|
"typedef bool (*pxl8_texture_rule)(const pxl8_vec3* normal, const pxl8_bsp_face* face, const pxl8_bsp* bsp);\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"typedef struct pxl8_world_texture {\n"
|
|
|
|
|
" char name[16];\n"
|
|
|
|
|
" unsigned int texture_id;\n"
|
|
|
|
|
" pxl8_texture_rule rule;\n"
|
|
|
|
|
"} pxl8_world_texture;\n"
|
|
|
|
|
"\n"
|
2025-10-07 10:32:48 -05:00
|
|
|
"typedef struct pxl8_world pxl8_world;\n"
|
|
|
|
|
"pxl8_world* pxl8_world_create(void);\n"
|
|
|
|
|
"void pxl8_world_destroy(pxl8_world* world);\n"
|
2025-11-11 21:24:53 -06:00
|
|
|
"int pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params);\n"
|
2025-11-21 16:44:42 -06:00
|
|
|
"int pxl8_world_load(pxl8_world* world, const char* path);\n"
|
|
|
|
|
"void pxl8_world_unload(pxl8_world* world);\n"
|
2025-11-11 21:24:53 -06:00
|
|
|
"int pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_texture* textures, unsigned int count);\n"
|
2025-11-21 16:44:42 -06:00
|
|
|
"bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, float radius);\n"
|
2025-11-09 06:30:17 -06:00
|
|
|
"bool pxl8_world_is_loaded(const pxl8_world* world);\n"
|
2025-10-07 10:32:48 -05:00
|
|
|
"void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n"
|
2025-11-21 16:44:42 -06:00
|
|
|
"pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n"
|
2025-10-07 10:32:48 -05:00
|
|
|
"\n"
|
2025-11-21 11:51:23 -06:00
|
|
|
"typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n"
|
|
|
|
|
"pxl8_gui_state* pxl8_gui_state_create(void);\n"
|
|
|
|
|
"void pxl8_gui_state_destroy(pxl8_gui_state* state);\n"
|
|
|
|
|
"void pxl8_gui_begin_frame(pxl8_gui_state* state);\n"
|
|
|
|
|
"void pxl8_gui_end_frame(pxl8_gui_state* state);\n"
|
|
|
|
|
"void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y);\n"
|
|
|
|
|
"void pxl8_gui_cursor_down(pxl8_gui_state* state);\n"
|
|
|
|
|
"void pxl8_gui_cursor_up(pxl8_gui_state* state);\n"
|
|
|
|
|
"bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label);\n"
|
|
|
|
|
"void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);\n"
|
|
|
|
|
"void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);\n"
|
|
|
|
|
"bool pxl8_gui_is_hovering(const pxl8_gui_state* state);\n"
|
|
|
|
|
"void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);\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) {
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_script* script = (pxl8_script*)calloc(1, sizeof(pxl8_script));
|
2025-10-04 04:13:48 -05:00
|
|
|
if (!script) return NULL;
|
|
|
|
|
|
|
|
|
|
script->L = luaL_newstate();
|
|
|
|
|
if (!script->L) {
|
2025-10-17 17:54:33 -05:00
|
|
|
free(script);
|
2025-10-04 04:13:48 -05:00
|
|
|
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");
|
2025-10-06 18:14:07 -05:00
|
|
|
|
|
|
|
|
lua_getglobal(script->L, "fennel");
|
|
|
|
|
lua_getfield(script->L, -1, "install");
|
|
|
|
|
if (lua_isfunction(script->L, -1)) {
|
|
|
|
|
if (lua_pcall(script->L, 0, 0, 0) != 0) {
|
|
|
|
|
pxl8_warn("Failed to install fennel searcher: %s", lua_tostring(script->L, -1));
|
|
|
|
|
lua_pop(script->L, 1);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
lua_pop(script->L, 1);
|
|
|
|
|
}
|
|
|
|
|
lua_pop(script->L, 1);
|
2025-10-04 04:13:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
script->last_error[0] = '\0';
|
|
|
|
|
return script;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_script_destroy(pxl8_script* script) {
|
|
|
|
|
if (!script) return;
|
|
|
|
|
if (script->L) {
|
|
|
|
|
lua_close(script->L);
|
|
|
|
|
}
|
2025-10-17 17:54:33 -05:00
|
|
|
free(script);
|
2025-10-04 04:13:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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-11-18 23:50:02 -06:00
|
|
|
void pxl8_script_set_sys(pxl8_script* script, void* sys) {
|
2025-11-11 23:26:51 -06:00
|
|
|
if (!script) return;
|
2025-11-18 23:50:02 -06:00
|
|
|
if (script->L && sys) {
|
|
|
|
|
lua_pushlightuserdata(script->L, sys);
|
|
|
|
|
lua_setglobal(script->L, "_pxl8_sys");
|
2025-11-11 23:26:51 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
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';
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
char* last_slash = strrchr(filename_copy, '/');
|
|
|
|
|
|
|
|
|
|
if (last_slash) {
|
|
|
|
|
*last_slash = '\0';
|
2025-11-01 12:39:59 -05:00
|
|
|
char script_dir[PATH_MAX];
|
|
|
|
|
char original_cwd[PATH_MAX];
|
|
|
|
|
|
|
|
|
|
if (realpath(filename_copy, script_dir) && getcwd(original_cwd, sizeof(original_cwd))) {
|
2025-10-04 04:13:48 -05:00
|
|
|
chdir(script_dir);
|
|
|
|
|
pxl8_script_set_cart_path(script, script_dir, original_cwd);
|
2025-11-01 12:39:59 -05:00
|
|
|
strncpy(out_basename, last_slash + 1, basename_size - 1);
|
|
|
|
|
out_basename[basename_size - 1] = '\0';
|
|
|
|
|
} else {
|
|
|
|
|
strncpy(out_basename, filename, basename_size - 1);
|
|
|
|
|
out_basename[basename_size - 1] = '\0';
|
2025-10-04 04:13:48 -05:00
|
|
|
}
|
2025-11-01 12:39:59 -05:00
|
|
|
} else {
|
|
|
|
|
strncpy(out_basename, filename, basename_size - 1);
|
|
|
|
|
out_basename[basename_size - 1] = '\0';
|
2025-10-04 04:13:48 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 11:55:04 -05:00
|
|
|
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;
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
char basename[PATH_MAX];
|
|
|
|
|
pxl8_script_prepare_path(script, filename, basename, sizeof(basename));
|
2025-10-04 11:55:04 -05:00
|
|
|
|
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';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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-11-01 12:39:59 -05:00
|
|
|
char basename[PATH_MAX];
|
|
|
|
|
pxl8_script_prepare_path(script, filename, basename, sizeof(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);
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
static pxl8_result pxl8_script_eval_internal(pxl8_script* script, const char* code, bool repl_mode) {
|
2025-10-04 04:13:48 -05:00
|
|
|
if (!script || !script->L || !code) return PXL8_ERROR_NULL_POINTER;
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
char transformed_code[PXL8_MAX_REPL_COMMAND_SIZE];
|
|
|
|
|
if (repl_mode) {
|
|
|
|
|
pxl8_script_repl_promote_locals(code, transformed_code, sizeof(transformed_code));
|
|
|
|
|
code = transformed_code;
|
|
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
|
return PXL8_ERROR_SCRIPT_ERROR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lua_getfield(script->L, -1, "eval");
|
|
|
|
|
lua_pushstring(script->L, code);
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
lua_newtable(script->L);
|
|
|
|
|
lua_pushstring(script->L, "useMetadata");
|
|
|
|
|
lua_pushboolean(script->L, true);
|
|
|
|
|
lua_settable(script->L, -3);
|
|
|
|
|
|
|
|
|
|
if (lua_pcall(script->L, 2, 1, 0) != 0) {
|
|
|
|
|
const char* error = lua_tostring(script->L, -1);
|
|
|
|
|
if (error) {
|
|
|
|
|
char cleaned_error[2048];
|
|
|
|
|
size_t j = 0;
|
|
|
|
|
for (size_t i = 0; error[i] && j < sizeof(cleaned_error) - 1; i++) {
|
|
|
|
|
if (error[i] == '\t') {
|
|
|
|
|
cleaned_error[j++] = ' ';
|
|
|
|
|
} else {
|
|
|
|
|
cleaned_error[j++] = error[i];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cleaned_error[j] = '\0';
|
|
|
|
|
pxl8_script_set_error(script, cleaned_error);
|
|
|
|
|
} else {
|
|
|
|
|
pxl8_script_set_error(script, "Unknown error");
|
|
|
|
|
}
|
|
|
|
|
lua_pop(script->L, 2);
|
2025-10-04 04:13:48 -05:00
|
|
|
return PXL8_ERROR_SCRIPT_ERROR;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
if (!lua_isnil(script->L, -1)) {
|
|
|
|
|
lua_pushvalue(script->L, -1);
|
|
|
|
|
lua_setglobal(script->L, "_");
|
|
|
|
|
|
|
|
|
|
lua_getglobal(script->L, "tostring");
|
|
|
|
|
lua_pushvalue(script->L, -2);
|
|
|
|
|
if (lua_pcall(script->L, 1, 1, 0) == 0) {
|
|
|
|
|
const char* result = lua_tostring(script->L, -1);
|
|
|
|
|
if (result && strlen(result) > 0) {
|
|
|
|
|
printf("=> %s\n", result);
|
|
|
|
|
fflush(stdout);
|
|
|
|
|
}
|
|
|
|
|
lua_pop(script->L, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lua_pop(script->L, 2);
|
2025-10-04 04:13:48 -05:00
|
|
|
script->last_error[0] = '\0';
|
|
|
|
|
return PXL8_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
pxl8_result pxl8_script_eval(pxl8_script* script, const char* code) {
|
|
|
|
|
return pxl8_script_eval_internal(script, code, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_result pxl8_script_eval_repl(pxl8_script* script, const char* code) {
|
|
|
|
|
return pxl8_script_eval_internal(script, code, true);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
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;
|
|
|
|
|
|
2025-11-21 11:51:23 -06:00
|
|
|
char full_path[512];
|
|
|
|
|
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-11-21 11:51:23 -06:00
|
|
|
struct stat st;
|
|
|
|
|
if (stat(full_path, &st) == 0) {
|
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
|
|
|
|
time_t subdir_time = get_latest_script_mod_time(full_path);
|
|
|
|
|
if (subdir_time > latest) {
|
|
|
|
|
latest = subdir_time;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
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) {
|
|
|
|
|
time_t mod_time = get_file_mod_time(full_path);
|
|
|
|
|
if (mod_time > latest) {
|
|
|
|
|
latest = mod_time;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
} else {
|
2025-11-11 21:24:53 -06:00
|
|
|
pxl8_error("Failed to load script: %s", script->last_error);
|
2025-10-04 04:13:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2025-10-17 17:54:33 -05:00
|
|
|
|
|
|
|
|
#include <pthread.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <linenoise.h>
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
#define PXL8_REPL_RING_BUFFER_SIZE 64
|
2025-10-17 17:54:33 -05:00
|
|
|
|
|
|
|
|
struct pxl8_script_repl_command {
|
2025-11-01 12:39:59 -05:00
|
|
|
char buffer[PXL8_MAX_REPL_COMMAND_SIZE];
|
2025-10-17 17:54:33 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct pxl8_script_repl {
|
|
|
|
|
pthread_t thread;
|
|
|
|
|
pthread_mutex_t mutex;
|
2025-11-01 12:39:59 -05:00
|
|
|
pthread_cond_t cond;
|
|
|
|
|
pxl8_script_repl_command ring_buffer[PXL8_REPL_RING_BUFFER_SIZE];
|
|
|
|
|
u32 head;
|
|
|
|
|
u32 tail;
|
2025-10-17 17:54:33 -05:00
|
|
|
bool running;
|
2025-11-01 12:39:59 -05:00
|
|
|
bool should_quit;
|
|
|
|
|
bool waiting_for_eval;
|
|
|
|
|
char accumulator[PXL8_MAX_REPL_COMMAND_SIZE];
|
2025-10-17 17:54:33 -05:00
|
|
|
};
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
static bool pxl8_script_is_incomplete_error(const char* error) {
|
|
|
|
|
if (!error) return false;
|
|
|
|
|
return strstr(error, "expected closing delimiter") != NULL ||
|
|
|
|
|
strstr(error, "unexpected end of source") != NULL ||
|
|
|
|
|
strstr(error, "expected whitespace before") != NULL ||
|
|
|
|
|
strstr(error, "unexpected end") != NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
static void pxl8_script_repl_completion(const char* buf, linenoiseCompletions* lc) {
|
|
|
|
|
const char* fennel_keywords[] = {
|
|
|
|
|
"fn", "let", "var", "set", "global", "local",
|
|
|
|
|
"if", "when", "do", "while", "for", "each",
|
|
|
|
|
"lambda", "λ", "partial", "macro", "macros",
|
|
|
|
|
"require", "include", "import-macros",
|
|
|
|
|
"values", "select", "table", "length",
|
|
|
|
|
".", "..", ":", "->", "->>", "-?>", "-?>>",
|
|
|
|
|
"doto", "match", "case", "pick-values",
|
|
|
|
|
"collect", "icollect", "accumulate"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const char* pxl8_functions[] = {
|
|
|
|
|
"pxl8.clr", "pxl8.pixel", "pxl8.get_pixel",
|
|
|
|
|
"pxl8.line", "pxl8.rect", "pxl8.rect_fill",
|
|
|
|
|
"pxl8.circle", "pxl8.circle_fill", "pxl8.text",
|
|
|
|
|
"pxl8.get_screen", "pxl8.info", "pxl8.warn",
|
|
|
|
|
"pxl8.error", "pxl8.debug", "pxl8.trace"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
size_t buf_len = strlen(buf);
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < sizeof(fennel_keywords) / sizeof(fennel_keywords[0]); i++) {
|
|
|
|
|
if (strncmp(buf, fennel_keywords[i], buf_len) == 0) {
|
|
|
|
|
linenoiseAddCompletion(lc, fennel_keywords[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < sizeof(pxl8_functions) / sizeof(pxl8_functions[0]); i++) {
|
|
|
|
|
if (strncmp(buf, pxl8_functions[i], buf_len) == 0) {
|
|
|
|
|
linenoiseAddCompletion(lc, pxl8_functions[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static char* pxl8_script_repl_hints(const char* buf, int* color, int* bold) {
|
|
|
|
|
if (strncmp(buf, "pxl8.", 5) == 0 && strlen(buf) == 5) {
|
|
|
|
|
*color = 35;
|
|
|
|
|
*bold = 0;
|
|
|
|
|
return "clr|pixel|line|rect|circle|text|get_screen";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcmp(buf, "(fn") == 0) {
|
|
|
|
|
*color = 36;
|
|
|
|
|
*bold = 0;
|
|
|
|
|
return " [args] body)";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcmp(buf, "(let") == 0) {
|
|
|
|
|
*color = 36;
|
|
|
|
|
*bold = 0;
|
|
|
|
|
return " [bindings] body)";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void* pxl8_script_repl_stdin_thread(void* user_data) {
|
|
|
|
|
pxl8_script_repl* repl = (pxl8_script_repl*)user_data;
|
|
|
|
|
char* line;
|
|
|
|
|
const char* history_file = ".pxl8_history";
|
|
|
|
|
|
|
|
|
|
linenoiseHistorySetMaxLen(100);
|
|
|
|
|
linenoiseSetMultiLine(1);
|
|
|
|
|
linenoiseSetCompletionCallback(pxl8_script_repl_completion);
|
|
|
|
|
linenoiseSetHintsCallback(pxl8_script_repl_hints);
|
|
|
|
|
linenoiseHistoryLoad(history_file);
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
while (repl->running) {
|
|
|
|
|
pthread_mutex_lock(&repl->mutex);
|
|
|
|
|
bool in_multiline = (repl->accumulator[0] != '\0');
|
|
|
|
|
pthread_mutex_unlock(&repl->mutex);
|
|
|
|
|
|
|
|
|
|
const char* prompt = in_multiline ? ".. " : ">> ";
|
|
|
|
|
line = linenoise(prompt);
|
|
|
|
|
|
|
|
|
|
if (!line) break;
|
|
|
|
|
|
|
|
|
|
if (strlen(line) > 0 || in_multiline) {
|
|
|
|
|
if (!in_multiline) {
|
|
|
|
|
linenoiseHistoryAdd(line);
|
|
|
|
|
linenoiseHistorySave(history_file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pthread_mutex_lock(&repl->mutex);
|
|
|
|
|
|
|
|
|
|
if (repl->accumulator[0] != '\0') {
|
|
|
|
|
strncat(repl->accumulator, "\n", PXL8_MAX_REPL_COMMAND_SIZE - strlen(repl->accumulator) - 1);
|
|
|
|
|
}
|
|
|
|
|
strncat(repl->accumulator, line, PXL8_MAX_REPL_COMMAND_SIZE - strlen(repl->accumulator) - 1);
|
|
|
|
|
|
|
|
|
|
u32 next_tail = (repl->tail + 1) % PXL8_REPL_RING_BUFFER_SIZE;
|
|
|
|
|
if (next_tail != repl->head) {
|
2025-11-10 09:59:42 -06:00
|
|
|
size_t len = strlen(repl->accumulator);
|
|
|
|
|
size_t copy_len = len < PXL8_MAX_REPL_COMMAND_SIZE - 1 ? len : PXL8_MAX_REPL_COMMAND_SIZE - 1;
|
|
|
|
|
memcpy(repl->ring_buffer[repl->tail].buffer, repl->accumulator, copy_len);
|
|
|
|
|
repl->ring_buffer[repl->tail].buffer[copy_len] = '\0';
|
2025-11-01 12:39:59 -05:00
|
|
|
repl->tail = next_tail;
|
|
|
|
|
repl->waiting_for_eval = true;
|
|
|
|
|
|
|
|
|
|
while (repl->waiting_for_eval && repl->running) {
|
|
|
|
|
pthread_cond_wait(&repl->cond, &repl->mutex);
|
2025-10-17 17:54:33 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-11-01 12:39:59 -05:00
|
|
|
pthread_mutex_unlock(&repl->mutex);
|
2025-10-17 17:54:33 -05:00
|
|
|
}
|
|
|
|
|
linenoiseFree(line);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
pthread_mutex_lock(&repl->mutex);
|
|
|
|
|
repl->should_quit = true;
|
|
|
|
|
pthread_mutex_unlock(&repl->mutex);
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_script_repl* pxl8_script_repl_create(void) {
|
|
|
|
|
pxl8_script_repl* repl = (pxl8_script_repl*)calloc(1, sizeof(pxl8_script_repl));
|
|
|
|
|
return repl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_script_repl_destroy(pxl8_script_repl* repl) {
|
|
|
|
|
if (!repl) return;
|
|
|
|
|
free(repl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_script_repl_init(pxl8_script_repl* repl) {
|
|
|
|
|
if (!repl) return;
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
repl->head = 0;
|
|
|
|
|
repl->tail = 0;
|
2025-10-17 17:54:33 -05:00
|
|
|
repl->running = true;
|
2025-11-01 12:39:59 -05:00
|
|
|
repl->waiting_for_eval = false;
|
|
|
|
|
repl->accumulator[0] = '\0';
|
2025-10-17 17:54:33 -05:00
|
|
|
pthread_mutex_init(&repl->mutex, NULL);
|
2025-11-01 12:39:59 -05:00
|
|
|
pthread_cond_init(&repl->cond, NULL);
|
2025-10-17 17:54:33 -05:00
|
|
|
pthread_create(&repl->thread, NULL, pxl8_script_repl_stdin_thread, repl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_script_repl_shutdown(pxl8_script_repl* repl) {
|
|
|
|
|
if (!repl) return;
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
pthread_mutex_lock(&repl->mutex);
|
2025-10-17 17:54:33 -05:00
|
|
|
repl->running = false;
|
2025-11-01 12:39:59 -05:00
|
|
|
pthread_cond_signal(&repl->cond);
|
|
|
|
|
pthread_mutex_unlock(&repl->mutex);
|
|
|
|
|
|
|
|
|
|
pthread_cancel(repl->thread);
|
2025-10-17 17:54:33 -05:00
|
|
|
pthread_join(repl->thread, NULL);
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
system("stty sane 2>/dev/null");
|
|
|
|
|
|
|
|
|
|
pthread_cond_destroy(&repl->cond);
|
|
|
|
|
pthread_mutex_destroy(&repl->mutex);
|
2025-10-17 17:54:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_script_repl_command* pxl8_script_repl_pop_command(pxl8_script_repl* repl) {
|
|
|
|
|
if (!repl) return NULL;
|
|
|
|
|
|
|
|
|
|
pthread_mutex_lock(&repl->mutex);
|
2025-11-01 12:39:59 -05:00
|
|
|
pxl8_script_repl_command* cmd = NULL;
|
|
|
|
|
if (repl->head != repl->tail) {
|
|
|
|
|
cmd = &repl->ring_buffer[repl->head];
|
|
|
|
|
repl->head = (repl->head + 1) % PXL8_REPL_RING_BUFFER_SIZE;
|
2025-10-17 17:54:33 -05:00
|
|
|
}
|
|
|
|
|
pthread_mutex_unlock(&repl->mutex);
|
|
|
|
|
return cmd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char* pxl8_script_repl_command_buffer(pxl8_script_repl_command* cmd) {
|
|
|
|
|
return cmd ? cmd->buffer : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
bool pxl8_script_repl_should_quit(pxl8_script_repl* repl) {
|
|
|
|
|
if (!repl) return false;
|
|
|
|
|
|
|
|
|
|
pthread_mutex_lock(&repl->mutex);
|
|
|
|
|
bool should_quit = repl->should_quit;
|
|
|
|
|
pthread_mutex_unlock(&repl->mutex);
|
|
|
|
|
|
|
|
|
|
return should_quit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_script_repl_eval_complete(pxl8_script_repl* repl) {
|
|
|
|
|
if (!repl) return;
|
|
|
|
|
|
|
|
|
|
pthread_mutex_lock(&repl->mutex);
|
|
|
|
|
repl->waiting_for_eval = false;
|
|
|
|
|
pthread_cond_signal(&repl->cond);
|
|
|
|
|
pthread_mutex_unlock(&repl->mutex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_script_repl_clear_accumulator(pxl8_script_repl* repl) {
|
|
|
|
|
if (!repl) return;
|
|
|
|
|
|
|
|
|
|
pthread_mutex_lock(&repl->mutex);
|
|
|
|
|
repl->accumulator[0] = '\0';
|
|
|
|
|
pthread_mutex_unlock(&repl->mutex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool pxl8_script_is_incomplete_input(pxl8_script* script) {
|
|
|
|
|
if (!script) return false;
|
|
|
|
|
return pxl8_script_is_incomplete_error(script->last_error);
|
2025-10-17 17:54:33 -05:00
|
|
|
}
|