add save and bundle pxl8 with game for standalone game distribution
This commit is contained in:
parent
b1e8525c3e
commit
04d3af11a9
25 changed files with 1173 additions and 346 deletions
|
|
@ -12,6 +12,7 @@
|
|||
#include <lua.h>
|
||||
#include <lualib.h>
|
||||
|
||||
#include "pxl8_cart.h"
|
||||
#include "pxl8_embed.h"
|
||||
#include "pxl8_macros.h"
|
||||
#include "pxl8_gui.h"
|
||||
|
|
@ -24,10 +25,91 @@ struct pxl8_script {
|
|||
char main_path[PXL8_MAX_PATH];
|
||||
char watch_dir[PXL8_MAX_PATH];
|
||||
time_t latest_mod_time;
|
||||
bool repl_mode;
|
||||
};
|
||||
|
||||
#define PXL8_MAX_REPL_COMMAND_SIZE 4096
|
||||
|
||||
static int pxl8_cart_loader(lua_State* L) {
|
||||
const char* found_path = lua_tostring(L, lua_upvalueindex(1));
|
||||
const char* code = lua_tostring(L, lua_upvalueindex(2));
|
||||
size_t code_len = lua_objlen(L, lua_upvalueindex(2));
|
||||
bool is_fennel = lua_toboolean(L, lua_upvalueindex(3));
|
||||
|
||||
if (is_fennel) {
|
||||
lua_getglobal(L, "fennel");
|
||||
lua_getfield(L, -1, "eval");
|
||||
lua_pushlstring(L, code, code_len);
|
||||
lua_createtable(L, 0, 1);
|
||||
lua_pushstring(L, found_path);
|
||||
lua_setfield(L, -2, "filename");
|
||||
|
||||
if (lua_pcall(L, 2, 1, 0) != 0) {
|
||||
lua_remove(L, -2);
|
||||
return lua_error(L);
|
||||
}
|
||||
lua_remove(L, -2);
|
||||
} else {
|
||||
if (luaL_loadbuffer(L, code, code_len, found_path) != 0) {
|
||||
return lua_error(L);
|
||||
}
|
||||
if (lua_pcall(L, 0, 1, 0) != 0) {
|
||||
return lua_error(L);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int pxl8_cart_searcher(lua_State* L) {
|
||||
const char* modname = luaL_checkstring(L, 1);
|
||||
|
||||
pxl8_cart* cart = pxl8_cart_current();
|
||||
if (!cart || !pxl8_cart_is_packed(cart)) {
|
||||
lua_pushstring(L, "\n\tno packed cart mounted");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char path[512];
|
||||
size_t len = strlen(modname);
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < len && j < sizeof(path) - 5; i++) {
|
||||
if (modname[i] == '.') {
|
||||
path[j++] = '/';
|
||||
} else {
|
||||
path[j++] = modname[i];
|
||||
}
|
||||
}
|
||||
path[j] = '\0';
|
||||
|
||||
const char* extensions[] = {".fnl", ".lua", "/init.fnl", "/init.lua"};
|
||||
u8* data = NULL;
|
||||
u32 size = 0;
|
||||
char found_path[512];
|
||||
bool is_fennel = false;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
snprintf(found_path, sizeof(found_path), "%s%s", path, extensions[i]);
|
||||
if (pxl8_cart_read_file(cart, found_path, &data, &size) == PXL8_OK) {
|
||||
is_fennel = (i == 0 || i == 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
lua_pushfstring(L, "\n\tno file '%s' in cart", path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
lua_pushstring(L, found_path);
|
||||
lua_pushlstring(L, (const char*)data, size);
|
||||
lua_pushboolean(L, is_fennel);
|
||||
pxl8_cart_free_file(data);
|
||||
|
||||
lua_pushcclosure(L, pxl8_cart_loader, 3);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void pxl8_script_repl_promote_locals(const char* input, char* output, size_t output_size) {
|
||||
size_t i = 0;
|
||||
size_t j = 0;
|
||||
|
|
@ -100,7 +182,13 @@ static const char* pxl8_ffi_cdefs =
|
|||
"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_cycle_palette(pxl8_gfx* ctx, u8 start, u8 count, i32 step);\n"
|
||||
"void pxl8_gfx_fade_palette(pxl8_gfx* ctx, u8 start, u8 count, f32 amount, u32 target_color);\n"
|
||||
"i32 pxl8_gfx_add_palette_cycle(pxl8_gfx* ctx, u8 start_index, u8 end_index, f32 speed);\n"
|
||||
"void pxl8_gfx_remove_palette_cycle(pxl8_gfx* ctx, i32 cycle_id);\n"
|
||||
"void pxl8_gfx_set_palette_cycle_speed(pxl8_gfx* ctx, i32 cycle_id, f32 speed);\n"
|
||||
"void pxl8_gfx_clear_palette_cycles(pxl8_gfx* ctx);\n"
|
||||
"void pxl8_gfx_update(pxl8_gfx* ctx, f32 dt);\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"
|
||||
"i32 pxl8_gfx_create_texture(pxl8_gfx* ctx, const u8* pixels, u32 width, u32 height);\n"
|
||||
|
|
@ -323,7 +411,17 @@ static const char* pxl8_ffi_cdefs =
|
|||
"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";
|
||||
"void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_save pxl8_save;\n"
|
||||
"pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version);\n"
|
||||
"void pxl8_save_destroy(pxl8_save* save);\n"
|
||||
"i32 pxl8_save_write(pxl8_save* save, u8 slot, const u8* data, u32 size);\n"
|
||||
"i32 pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_out);\n"
|
||||
"void pxl8_save_free(u8* data);\n"
|
||||
"bool pxl8_save_exists(pxl8_save* save, u8 slot);\n"
|
||||
"i32 pxl8_save_delete(pxl8_save* save, u8 slot);\n"
|
||||
"const char* pxl8_save_get_directory(pxl8_save* save);\n";
|
||||
|
||||
void pxl8_lua_info(const char* msg) {
|
||||
pxl8_info("%s", msg);
|
||||
|
|
@ -387,9 +485,10 @@ static void pxl8_install_embed_searcher(lua_State* L) {
|
|||
lua_pop(L, 2);
|
||||
}
|
||||
|
||||
pxl8_script* pxl8_script_create(void) {
|
||||
pxl8_script* pxl8_script_create(bool repl_mode) {
|
||||
pxl8_script* script = (pxl8_script*)calloc(1, sizeof(pxl8_script));
|
||||
if (!script) return NULL;
|
||||
script->repl_mode = repl_mode;
|
||||
|
||||
script->L = luaL_newstate();
|
||||
if (!script->L) {
|
||||
|
|
@ -427,10 +526,42 @@ pxl8_script* pxl8_script_create(void) {
|
|||
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) {
|
||||
// Pass options table for fennel.install()
|
||||
// lambdaAsFn: removes arity checking overhead from fn/lambda
|
||||
// correlate: aligns Lua line numbers with Fennel source
|
||||
// useMetadata: enables docstrings (only in REPL mode for perf)
|
||||
lua_newtable(script->L);
|
||||
lua_pushboolean(script->L, 1);
|
||||
lua_setfield(script->L, -2, "lambdaAsFn");
|
||||
lua_pushboolean(script->L, 1);
|
||||
lua_setfield(script->L, -2, "correlate");
|
||||
if (script->repl_mode) {
|
||||
lua_pushboolean(script->L, 1);
|
||||
lua_setfield(script->L, -2, "useMetadata");
|
||||
lua_pushboolean(script->L, 1);
|
||||
lua_setfield(script->L, -2, "assertAsRepl");
|
||||
}
|
||||
if (lua_pcall(script->L, 1, 0, 0) != 0) {
|
||||
pxl8_warn("Failed to install fennel searcher: %s", lua_tostring(script->L, -1));
|
||||
lua_pop(script->L, 1);
|
||||
}
|
||||
|
||||
lua_getglobal(script->L, "package");
|
||||
lua_getfield(script->L, -1, "loaders");
|
||||
if (lua_isnil(script->L, -1)) {
|
||||
lua_pop(script->L, 1);
|
||||
lua_getfield(script->L, -1, "searchers");
|
||||
}
|
||||
if (lua_istable(script->L, -1)) {
|
||||
int n = (int)lua_objlen(script->L, -1);
|
||||
for (int i = n; i >= 2; i--) {
|
||||
lua_rawgeti(script->L, -1, i);
|
||||
lua_rawseti(script->L, -2, i + 1);
|
||||
}
|
||||
lua_pushcfunction(script->L, pxl8_cart_searcher);
|
||||
lua_rawseti(script->L, -2, 2);
|
||||
}
|
||||
lua_pop(script->L, 2);
|
||||
} else {
|
||||
lua_pop(script->L, 1);
|
||||
}
|
||||
|
|
@ -574,16 +705,45 @@ pxl8_result pxl8_script_run_fennel_file(pxl8_script* script, const char* filenam
|
|||
return PXL8_ERROR_SCRIPT_ERROR;
|
||||
}
|
||||
|
||||
lua_getfield(script->L, -1, "dofile");
|
||||
lua_pushstring(script->L, basename);
|
||||
|
||||
pxl8_cart* cart = pxl8_cart_current();
|
||||
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;
|
||||
|
||||
if (cart && pxl8_cart_is_packed(cart)) {
|
||||
u8* data = NULL;
|
||||
u32 size = 0;
|
||||
if (pxl8_cart_read_file(cart, basename, &data, &size) != PXL8_OK) {
|
||||
pxl8_script_set_error(script, "Failed to read script from cart");
|
||||
lua_pop(script->L, 1);
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
lua_getfield(script->L, -1, "eval");
|
||||
lua_pushlstring(script->L, (const char*)data, size);
|
||||
|
||||
lua_createtable(script->L, 0, 1);
|
||||
lua_pushstring(script->L, basename);
|
||||
lua_setfield(script->L, -2, "filename");
|
||||
|
||||
pxl8_cart_free_file(data);
|
||||
|
||||
if (lua_pcall(script->L, 2, 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';
|
||||
}
|
||||
} else {
|
||||
script->last_error[0] = '\0';
|
||||
lua_getfield(script->L, -1, "dofile");
|
||||
lua_pushstring(script->L, basename);
|
||||
|
||||
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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue