1291 lines
39 KiB
C
1291 lines
39 KiB
C
#include "pxl8_script.h"
|
|
|
|
#include <limits.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <dirent.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include <lauxlib.h>
|
|
#include <lua.h>
|
|
#include <lualib.h>
|
|
|
|
#include "pxl8_cart.h"
|
|
#include "pxl8_embed.h"
|
|
#include "pxl8_gui.h"
|
|
#include "pxl8_log.h"
|
|
#include "pxl8_macros.h"
|
|
#include "pxl8_mem.h"
|
|
#include "pxl8_script_ffi.h"
|
|
|
|
struct pxl8_script {
|
|
lua_State* L;
|
|
pxl8_gfx* gfx;
|
|
pxl8_input_state* input;
|
|
pxl8_sfx_mixer* mixer;
|
|
char last_error[PXL8_MAX_ERROR_SIZE];
|
|
char main_path[PXL8_MAX_PATH];
|
|
char watch_dir[PXL8_MAX_PATH];
|
|
time_t latest_mod_time;
|
|
int repl_env_ref;
|
|
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));
|
|
usize 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_get_cart();
|
|
if (!cart || !pxl8_cart_is_packed(cart)) {
|
|
lua_pushstring(L, "\n\tno packed cart mounted");
|
|
return 1;
|
|
}
|
|
|
|
char path[512];
|
|
usize len = strlen(modname);
|
|
usize j = 0;
|
|
for (usize 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, usize output_size) {
|
|
usize i = 0;
|
|
usize 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) {
|
|
memcpy(&output[j], "(global ", 8);
|
|
j += 8;
|
|
i += 7;
|
|
continue;
|
|
}
|
|
|
|
output[j++] = input[i++];
|
|
}
|
|
output[j] = '\0';
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
}
|
|
|
|
static void pxl8_script_set_error(pxl8_script* script, const char* error) {
|
|
if (!script) return;
|
|
if (!error) {
|
|
snprintf(script->last_error, sizeof(script->last_error), "Unknown error");
|
|
return;
|
|
}
|
|
|
|
const char* src = error;
|
|
char* dst = script->last_error;
|
|
char* end = dst + sizeof(script->last_error) - 1;
|
|
|
|
while (*src && dst < end) {
|
|
if (strncmp(src, "[string \"", 9) == 0) {
|
|
src += 9;
|
|
const char* mod_start = src;
|
|
while (*src && !(*src == '"' && *(src+1) == ']')) src++;
|
|
usize mod_len = src - mod_start;
|
|
|
|
if (mod_len > 4 && strncmp(mod_start, "pxl8", 4) == 0) {
|
|
const char* prefix = "src/lua/";
|
|
while (*prefix && dst < end) *dst++ = *prefix++;
|
|
for (usize i = 0; i < mod_len && dst < end; i++) {
|
|
*dst++ = (mod_start[i] == '.') ? '/' : mod_start[i];
|
|
}
|
|
const char* suffix = ".lua";
|
|
while (*suffix && dst < end) *dst++ = *suffix++;
|
|
} else {
|
|
for (usize i = 0; i < mod_len && dst < end; i++) {
|
|
*dst++ = mod_start[i];
|
|
}
|
|
}
|
|
|
|
if (*src == '"' && *(src+1) == ']') src += 2;
|
|
} else {
|
|
*dst++ = *src++;
|
|
}
|
|
}
|
|
*dst = '\0';
|
|
}
|
|
|
|
const char* pxl8_script_get_last_error(pxl8_script* script) {
|
|
return script ? script->last_error : "Invalid script context";
|
|
}
|
|
|
|
static int pxl8_embed_searcher(lua_State* L) {
|
|
const char* name = luaL_checkstring(L, 1);
|
|
const pxl8_embed* e = pxl8_embed_find(name);
|
|
if (!e) {
|
|
lua_pushfstring(L, "\n\tno embedded module '%s'", name);
|
|
return 1;
|
|
}
|
|
if (luaL_loadbuffer(L, e->data, e->size, name) != 0) {
|
|
return lua_error(L);
|
|
}
|
|
lua_pushstring(L, name);
|
|
return 2;
|
|
}
|
|
|
|
static void pxl8_install_embed_searcher(lua_State* L) {
|
|
lua_getglobal(L, "package");
|
|
lua_getfield(L, -1, "searchers");
|
|
if (lua_isnil(L, -1)) {
|
|
lua_pop(L, 1);
|
|
lua_getfield(L, -1, "loaders");
|
|
}
|
|
|
|
int len = (int)lua_objlen(L, -1);
|
|
for (int i = len; i >= 2; i--) {
|
|
lua_rawgeti(L, -1, i);
|
|
lua_rawseti(L, -2, i + 1);
|
|
}
|
|
|
|
lua_pushcfunction(L, pxl8_embed_searcher);
|
|
lua_rawseti(L, -2, 2);
|
|
lua_pop(L, 2);
|
|
}
|
|
|
|
pxl8_script* pxl8_script_create(bool repl_mode) {
|
|
pxl8_script* script = (pxl8_script*)pxl8_calloc(1, sizeof(pxl8_script));
|
|
if (!script) return NULL;
|
|
script->repl_mode = repl_mode;
|
|
|
|
script->L = luaL_newstate();
|
|
if (!script->L) {
|
|
pxl8_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_error("FFI require failed: %s", 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_error("FFI cdef failed: %s", lua_tostring(script->L, -1));
|
|
pxl8_script_destroy(script);
|
|
return NULL;
|
|
}
|
|
|
|
lua_pop(script->L, 1);
|
|
|
|
pxl8_install_embed_searcher(script->L);
|
|
|
|
const pxl8_embed* fennel = pxl8_embed_find("fennel");
|
|
if (fennel && luaL_loadbuffer(script->L, fennel->data, fennel->size, "fennel") == 0) {
|
|
if (lua_pcall(script->L, 0, 1, 0) == 0) {
|
|
lua_setglobal(script->L, "fennel");
|
|
|
|
lua_getglobal(script->L, "fennel");
|
|
lua_getfield(script->L, -1, "install");
|
|
if (lua_isfunction(script->L, -1)) {
|
|
lua_newtable(script->L);
|
|
lua_pushboolean(script->L, 1);
|
|
lua_setfield(script->L, -2, "correlate");
|
|
lua_pushboolean(script->L, 1);
|
|
lua_setfield(script->L, -2, "lambdaAsFn");
|
|
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);
|
|
}
|
|
lua_pop(script->L, 1);
|
|
} else {
|
|
pxl8_warn("Failed to execute fennel: %s", lua_tostring(script->L, -1));
|
|
lua_pop(script->L, 1);
|
|
}
|
|
}
|
|
|
|
script->last_error[0] = '\0';
|
|
script->repl_env_ref = LUA_NOREF;
|
|
|
|
if (script->repl_mode) {
|
|
lua_newtable(script->L);
|
|
lua_newtable(script->L);
|
|
lua_getglobal(script->L, "_G");
|
|
lua_setfield(script->L, -2, "__index");
|
|
lua_setmetatable(script->L, -2);
|
|
script->repl_env_ref = luaL_ref(script->L, LUA_REGISTRYINDEX);
|
|
}
|
|
|
|
return script;
|
|
}
|
|
|
|
void pxl8_script_destroy(pxl8_script* script) {
|
|
if (!script) return;
|
|
if (script->L) {
|
|
if (script->repl_env_ref != LUA_NOREF) {
|
|
luaL_unref(script->L, LUA_REGISTRYINDEX, script->repl_env_ref);
|
|
}
|
|
lua_close(script->L);
|
|
}
|
|
pxl8_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");
|
|
}
|
|
}
|
|
|
|
void pxl8_script_set_rng(pxl8_script* script, void* rng) {
|
|
if (!script) return;
|
|
if (script->L && rng) {
|
|
lua_pushlightuserdata(script->L, rng);
|
|
lua_setglobal(script->L, "pxl8_rng");
|
|
}
|
|
}
|
|
|
|
void pxl8_script_set_sfx(pxl8_script* script, pxl8_sfx_mixer* mixer) {
|
|
if (!script) return;
|
|
script->mixer = mixer;
|
|
if (script->L && mixer) {
|
|
lua_pushlightuserdata(script->L, mixer);
|
|
lua_setglobal(script->L, "pxl8_sfx");
|
|
}
|
|
}
|
|
|
|
void pxl8_script_set_sys(pxl8_script* script, void* sys) {
|
|
if (!script) return;
|
|
if (script->L && sys) {
|
|
lua_pushlightuserdata(script->L, sys);
|
|
lua_setglobal(script->L, "pxl8_sys");
|
|
}
|
|
}
|
|
|
|
static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* filename, char* out_basename, usize basename_size) {
|
|
char filename_copy[PATH_MAX];
|
|
pxl8_strncpy(filename_copy, filename, sizeof(filename_copy));
|
|
|
|
char* last_slash = strrchr(filename_copy, '/');
|
|
|
|
if (last_slash) {
|
|
*last_slash = '\0';
|
|
char script_dir[PATH_MAX];
|
|
char original_cwd[PATH_MAX];
|
|
|
|
if (realpath(filename_copy, script_dir) && getcwd(original_cwd, sizeof(original_cwd))) {
|
|
chdir(script_dir);
|
|
pxl8_script_set_cart_path(script, script_dir, original_cwd);
|
|
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';
|
|
}
|
|
} else {
|
|
strncpy(out_basename, filename, basename_size - 1);
|
|
out_basename[basename_size - 1] = '\0';
|
|
}
|
|
|
|
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[PATH_MAX];
|
|
pxl8_script_prepare_path(script, filename, basename, sizeof(basename));
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
char full_path[512];
|
|
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
|
|
|
|
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 {
|
|
usize 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
closedir(dir);
|
|
return latest;
|
|
}
|
|
|
|
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_strncpy(script->watch_dir, cart_path, sizeof(script->watch_dir));
|
|
script->latest_mod_time = get_latest_script_mod_time(script->watch_dir);
|
|
}
|
|
|
|
pxl8_result pxl8_script_run_fennel_file(pxl8_script* script, const char* filename) {
|
|
if (!script || !script->L || !filename) return PXL8_ERROR_NULL_POINTER;
|
|
|
|
char basename[PATH_MAX];
|
|
pxl8_script_prepare_path(script, filename, basename, sizeof(basename));
|
|
|
|
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;
|
|
}
|
|
|
|
pxl8_cart* cart = pxl8_get_cart();
|
|
pxl8_result result = PXL8_OK;
|
|
|
|
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 {
|
|
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);
|
|
|
|
return result;
|
|
}
|
|
|
|
static pxl8_result pxl8_script_eval_internal(pxl8_script* script, const char* code, bool repl_mode) {
|
|
if (!script || !script->L || !code) return PXL8_ERROR_NULL_POINTER;
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
|
|
lua_newtable(script->L);
|
|
lua_pushboolean(script->L, true);
|
|
lua_setfield(script->L, -2, "useMetadata");
|
|
|
|
if (repl_mode && script->repl_env_ref != LUA_NOREF) {
|
|
lua_rawgeti(script->L, LUA_REGISTRYINDEX, script->repl_env_ref);
|
|
lua_setfield(script->L, -2, "env");
|
|
lua_pushboolean(script->L, false);
|
|
lua_setfield(script->L, -2, "allowedGlobals");
|
|
}
|
|
|
|
if (lua_pcall(script->L, 2, 1, 0) != 0) {
|
|
const char* error = lua_tostring(script->L, -1);
|
|
if (error) {
|
|
char cleaned_error[2048];
|
|
usize j = 0;
|
|
for (usize 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);
|
|
return PXL8_ERROR_SCRIPT_ERROR;
|
|
}
|
|
|
|
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);
|
|
script->last_error[0] = '\0';
|
|
return PXL8_OK;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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 bool pxl8_script_is_builtin_global(const char* name) {
|
|
static const char* builtins[] = {
|
|
"_G", "_VERSION", "arg", "assert", "bit", "collectgarbage",
|
|
"coroutine", "debug", "dofile", "error", "fennel", "ffi",
|
|
"gcinfo", "getfenv", "getmetatable", "init", "io", "ipairs",
|
|
"jit", "load", "loadfile", "loadstring", "math", "module",
|
|
"newproxy", "next", "os", "package", "pairs", "pcall",
|
|
"print", "pxl8", "rawequal", "rawget", "rawset", "require",
|
|
"select", "setfenv", "setmetatable", "string", "table",
|
|
"tonumber", "tostring", "type", "unpack", "update", "frame",
|
|
"xpcall", "pxl8_gfx", "pxl8_input", "pxl8_rng", "pxl8_sfx", "pxl8_sys",
|
|
NULL
|
|
};
|
|
for (int i = 0; builtins[i]; i++) {
|
|
if (strcmp(name, builtins[i]) == 0) return true;
|
|
}
|
|
return name[0] == '_' && name[1] == '_';
|
|
}
|
|
|
|
static void pxl8_script_cleanup(pxl8_script* script) {
|
|
if (script->gfx) {
|
|
pxl8_gfx_clear_textures(script->gfx);
|
|
}
|
|
|
|
if (script->mixer) {
|
|
pxl8_sfx_mixer_clear(script->mixer);
|
|
}
|
|
}
|
|
|
|
static void pxl8_script_save_upvalues(lua_State* L, const char* func_name, int saved_table) {
|
|
lua_getglobal(L, func_name);
|
|
if (!lua_isfunction(L, -1)) {
|
|
lua_pop(L, 1);
|
|
return;
|
|
}
|
|
|
|
int func_idx = lua_gettop(L);
|
|
for (int i = 1; ; i++) {
|
|
const char* name = lua_getupvalue(L, func_idx, i);
|
|
if (!name) break;
|
|
|
|
int vtype = lua_type(L, -1);
|
|
if (name[0] != '(' &&
|
|
vtype != LUA_TFUNCTION &&
|
|
vtype != LUA_TUSERDATA &&
|
|
vtype != LUA_TLIGHTUSERDATA &&
|
|
vtype != LUA_TTHREAD &&
|
|
vtype != LUA_TTABLE) {
|
|
lua_pushstring(L, name);
|
|
lua_pushvalue(L, -2);
|
|
lua_settable(L, saved_table);
|
|
}
|
|
lua_pop(L, 1);
|
|
}
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
static void pxl8_script_restore_upvalues(lua_State* L, const char* func_name, int saved_table) {
|
|
lua_getglobal(L, func_name);
|
|
if (!lua_isfunction(L, -1)) {
|
|
lua_pop(L, 1);
|
|
return;
|
|
}
|
|
|
|
int func_idx = lua_gettop(L);
|
|
for (int i = 1; ; i++) {
|
|
const char* name = lua_getupvalue(L, func_idx, i);
|
|
if (!name) break;
|
|
lua_pop(L, 1);
|
|
|
|
lua_getfield(L, saved_table, name);
|
|
if (!lua_isnil(L, -1)) {
|
|
lua_setupvalue(L, func_idx, i);
|
|
} else {
|
|
lua_pop(L, 1);
|
|
}
|
|
}
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
static void pxl8_script_save_globals(pxl8_script* script) {
|
|
lua_State* L = script->L;
|
|
|
|
lua_newtable(L);
|
|
int saved_table = lua_gettop(L);
|
|
|
|
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
|
lua_pushnil(L);
|
|
while (lua_next(L, -2) != 0) {
|
|
if (lua_type(L, -2) == LUA_TSTRING) {
|
|
const char* name = lua_tostring(L, -2);
|
|
int vtype = lua_type(L, -1);
|
|
|
|
if (!pxl8_script_is_builtin_global(name) &&
|
|
vtype != LUA_TFUNCTION &&
|
|
vtype != LUA_TUSERDATA &&
|
|
vtype != LUA_TLIGHTUSERDATA &&
|
|
vtype != LUA_TTHREAD &&
|
|
vtype != 10) {
|
|
lua_pushvalue(L, -2);
|
|
lua_pushvalue(L, -2);
|
|
lua_settable(L, saved_table);
|
|
}
|
|
}
|
|
lua_pop(L, 1);
|
|
}
|
|
lua_pop(L, 1);
|
|
|
|
pxl8_script_save_upvalues(L, "update", saved_table);
|
|
pxl8_script_save_upvalues(L, "frame", saved_table);
|
|
|
|
lua_setfield(L, LUA_REGISTRYINDEX, "_pxl8_hotreload_state");
|
|
pxl8_debug("Hot reload state saved");
|
|
}
|
|
|
|
static void pxl8_script_restore_globals(pxl8_script* script) {
|
|
lua_State* L = script->L;
|
|
|
|
lua_getfield(L, LUA_REGISTRYINDEX, "_pxl8_hotreload_state");
|
|
if (lua_isnil(L, -1)) {
|
|
lua_pop(L, 1);
|
|
return;
|
|
}
|
|
|
|
int saved_table = lua_gettop(L);
|
|
|
|
lua_pushnil(L);
|
|
while (lua_next(L, saved_table) != 0) {
|
|
lua_pushvalue(L, -2);
|
|
lua_pushvalue(L, -2);
|
|
lua_settable(L, LUA_GLOBALSINDEX);
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
pxl8_script_restore_upvalues(L, "update", saved_table);
|
|
pxl8_script_restore_upvalues(L, "frame", saved_table);
|
|
|
|
lua_pop(L, 1);
|
|
|
|
lua_pushnil(L);
|
|
lua_setfield(L, LUA_REGISTRYINDEX, "_pxl8_hotreload_state");
|
|
pxl8_debug("Hot reload state restored");
|
|
}
|
|
|
|
#define SER_NIL 0
|
|
#define SER_BOOL 1
|
|
#define SER_NUMBER 2
|
|
#define SER_STRING 3
|
|
#define SER_TABLE 4
|
|
|
|
typedef struct {
|
|
u8* data;
|
|
u32 size;
|
|
u32 capacity;
|
|
} ser_buffer;
|
|
|
|
static void ser_buffer_init(ser_buffer* buf) {
|
|
buf->capacity = 1024;
|
|
buf->data = pxl8_malloc(buf->capacity);
|
|
buf->size = 0;
|
|
}
|
|
|
|
static void ser_buffer_grow(ser_buffer* buf, u32 needed) {
|
|
if (buf->size + needed > buf->capacity) {
|
|
while (buf->size + needed > buf->capacity) {
|
|
buf->capacity *= 2;
|
|
}
|
|
buf->data = pxl8_realloc(buf->data, buf->capacity);
|
|
}
|
|
}
|
|
|
|
static void ser_write_u8(ser_buffer* buf, u8 v) {
|
|
ser_buffer_grow(buf, 1);
|
|
buf->data[buf->size++] = v;
|
|
}
|
|
|
|
static void ser_write_u32(ser_buffer* buf, u32 v) {
|
|
ser_buffer_grow(buf, 4);
|
|
buf->data[buf->size++] = v & 0xFF;
|
|
buf->data[buf->size++] = (v >> 8) & 0xFF;
|
|
buf->data[buf->size++] = (v >> 16) & 0xFF;
|
|
buf->data[buf->size++] = (v >> 24) & 0xFF;
|
|
}
|
|
|
|
static void ser_write_f64(ser_buffer* buf, f64 v) {
|
|
ser_buffer_grow(buf, 8);
|
|
memcpy(buf->data + buf->size, &v, 8);
|
|
buf->size += 8;
|
|
}
|
|
|
|
static void ser_write_bytes(ser_buffer* buf, const void* data, u32 len) {
|
|
ser_buffer_grow(buf, len);
|
|
memcpy(buf->data + buf->size, data, len);
|
|
buf->size += len;
|
|
}
|
|
|
|
static void ser_write_value(ser_buffer* buf, lua_State* L, int idx, int depth) {
|
|
int t = lua_type(L, idx);
|
|
switch (t) {
|
|
case LUA_TNIL:
|
|
ser_write_u8(buf, SER_NIL);
|
|
break;
|
|
case LUA_TBOOLEAN:
|
|
ser_write_u8(buf, SER_BOOL);
|
|
ser_write_u8(buf, lua_toboolean(L, idx) ? 1 : 0);
|
|
break;
|
|
case LUA_TNUMBER:
|
|
ser_write_u8(buf, SER_NUMBER);
|
|
ser_write_f64(buf, lua_tonumber(L, idx));
|
|
break;
|
|
case LUA_TSTRING: {
|
|
usize len;
|
|
const char* str = lua_tolstring(L, idx, &len);
|
|
ser_write_u8(buf, SER_STRING);
|
|
ser_write_u32(buf, (u32)len);
|
|
ser_write_bytes(buf, str, (u32)len);
|
|
break;
|
|
}
|
|
case LUA_TTABLE: {
|
|
if (depth > 16) { ser_write_u8(buf, SER_NIL); break; }
|
|
ser_write_u8(buf, SER_TABLE);
|
|
lua_pushvalue(L, idx);
|
|
int tbl = lua_gettop(L);
|
|
lua_pushnil(L);
|
|
while (lua_next(L, tbl) != 0) {
|
|
ser_write_value(buf, L, -2, depth + 1);
|
|
ser_write_value(buf, L, -1, depth + 1);
|
|
lua_pop(L, 1);
|
|
}
|
|
lua_pop(L, 1);
|
|
ser_write_u8(buf, SER_NIL);
|
|
break;
|
|
}
|
|
default:
|
|
ser_write_u8(buf, SER_NIL);
|
|
break;
|
|
}
|
|
}
|
|
|
|
u32 pxl8_script_serialize_globals(pxl8_script* script, u8** out_data) {
|
|
if (!script || !out_data) return 0;
|
|
lua_State* L = script->L;
|
|
|
|
ser_buffer buf;
|
|
ser_buffer_init(&buf);
|
|
|
|
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
|
lua_pushnil(L);
|
|
while (lua_next(L, -2) != 0) {
|
|
if (lua_type(L, -2) == LUA_TSTRING) {
|
|
const char* name = lua_tostring(L, -2);
|
|
int vtype = lua_type(L, -1);
|
|
|
|
if (!pxl8_script_is_builtin_global(name) &&
|
|
vtype != LUA_TFUNCTION &&
|
|
vtype != LUA_TUSERDATA &&
|
|
vtype != LUA_TLIGHTUSERDATA &&
|
|
vtype != LUA_TTHREAD &&
|
|
vtype != 10) {
|
|
ser_write_value(&buf, L, -2, 0);
|
|
ser_write_value(&buf, L, -1, 0);
|
|
}
|
|
}
|
|
lua_pop(L, 1);
|
|
}
|
|
lua_pop(L, 1);
|
|
|
|
ser_write_u8(&buf, SER_NIL);
|
|
|
|
*out_data = buf.data;
|
|
return buf.size;
|
|
}
|
|
|
|
typedef struct {
|
|
const u8* data;
|
|
u32 size;
|
|
u32 pos;
|
|
} deser_buffer;
|
|
|
|
static u8 deser_read_u8(deser_buffer* buf) {
|
|
if (buf->pos >= buf->size) return 0;
|
|
return buf->data[buf->pos++];
|
|
}
|
|
|
|
static u32 deser_read_u32(deser_buffer* buf) {
|
|
if (buf->pos + 4 > buf->size) return 0;
|
|
u32 v = buf->data[buf->pos] | (buf->data[buf->pos+1] << 8) |
|
|
(buf->data[buf->pos+2] << 16) | (buf->data[buf->pos+3] << 24);
|
|
buf->pos += 4;
|
|
return v;
|
|
}
|
|
|
|
static f64 deser_read_f64(deser_buffer* buf) {
|
|
if (buf->pos + 8 > buf->size) return 0;
|
|
f64 v;
|
|
memcpy(&v, buf->data + buf->pos, 8);
|
|
buf->pos += 8;
|
|
return v;
|
|
}
|
|
|
|
static void deser_read_value(deser_buffer* buf, lua_State* L, int depth) {
|
|
u8 type = deser_read_u8(buf);
|
|
switch (type) {
|
|
case SER_NIL:
|
|
lua_pushnil(L);
|
|
break;
|
|
case SER_BOOL:
|
|
lua_pushboolean(L, deser_read_u8(buf));
|
|
break;
|
|
case SER_NUMBER:
|
|
lua_pushnumber(L, deser_read_f64(buf));
|
|
break;
|
|
case SER_STRING: {
|
|
u32 len = deser_read_u32(buf);
|
|
if (buf->pos + len <= buf->size) {
|
|
lua_pushlstring(L, (const char*)(buf->data + buf->pos), len);
|
|
buf->pos += len;
|
|
} else {
|
|
lua_pushnil(L);
|
|
}
|
|
break;
|
|
}
|
|
case SER_TABLE: {
|
|
if (depth > 16) { lua_pushnil(L); break; }
|
|
lua_newtable(L);
|
|
while (buf->pos < buf->size) {
|
|
u8 key_type = deser_read_u8(buf);
|
|
if (key_type == SER_NIL) break;
|
|
buf->pos--;
|
|
deser_read_value(buf, L, depth + 1);
|
|
deser_read_value(buf, L, depth + 1);
|
|
lua_settable(L, -3);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
lua_pushnil(L);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void pxl8_script_deserialize_globals(pxl8_script* script, const u8* data, u32 size) {
|
|
if (!script || !data || size == 0) return;
|
|
lua_State* L = script->L;
|
|
|
|
deser_buffer buf = { .data = data, .size = size, .pos = 0 };
|
|
|
|
while (buf.pos < buf.size) {
|
|
u8 key_type = deser_read_u8(&buf);
|
|
if (key_type == SER_NIL) break;
|
|
buf.pos--;
|
|
|
|
deser_read_value(&buf, L, 0);
|
|
deser_read_value(&buf, L, 0);
|
|
lua_settable(L, LUA_GLOBALSINDEX);
|
|
}
|
|
|
|
pxl8_debug("Deserialized globals from %u bytes", size);
|
|
}
|
|
|
|
void pxl8_script_free_serialized(u8* data) {
|
|
pxl8_free(data);
|
|
}
|
|
|
|
pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path) {
|
|
if (!script || !path) return PXL8_ERROR_NULL_POINTER;
|
|
|
|
pxl8_strncpy(script->main_path, path, sizeof(script->main_path));
|
|
|
|
script->watch_dir[0] = '.';
|
|
script->watch_dir[1] = '\0';
|
|
|
|
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 {
|
|
pxl8_error("Failed to load script: %s", script->last_error);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void pxl8_script_clear_cart_modules(pxl8_script* script) {
|
|
lua_State* L = script->L;
|
|
|
|
lua_getglobal(L, "package");
|
|
lua_getfield(L, -1, "loaded");
|
|
|
|
lua_pushnil(L);
|
|
while (lua_next(L, -2) != 0) {
|
|
lua_pop(L, 1);
|
|
const char* name = lua_tostring(L, -1);
|
|
if (name && strncmp(name, "pxl8", 4) != 0 &&
|
|
strcmp(name, "fennel") != 0 &&
|
|
strcmp(name, "ffi") != 0 &&
|
|
strcmp(name, "bit") != 0 &&
|
|
strcmp(name, "jit") != 0 &&
|
|
strcmp(name, "string") != 0 &&
|
|
strcmp(name, "table") != 0 &&
|
|
strcmp(name, "math") != 0 &&
|
|
strcmp(name, "io") != 0 &&
|
|
strcmp(name, "os") != 0 &&
|
|
strcmp(name, "debug") != 0 &&
|
|
strcmp(name, "coroutine") != 0 &&
|
|
strcmp(name, "package") != 0) {
|
|
lua_pushvalue(L, -1);
|
|
lua_pushnil(L);
|
|
lua_settable(L, -4);
|
|
}
|
|
}
|
|
|
|
lua_pop(L, 2);
|
|
}
|
|
|
|
bool pxl8_script_check_reload(pxl8_script* script) {
|
|
if (!script || script->main_path[0] == '\0') {
|
|
return false;
|
|
}
|
|
|
|
static u32 frame_counter = 0;
|
|
if (++frame_counter % 60 != 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;
|
|
|
|
pxl8_script_cleanup(script);
|
|
pxl8_script_save_globals(script);
|
|
pxl8_script_clear_cart_modules(script);
|
|
|
|
const char* ext = strrchr(script->main_path, '.');
|
|
bool reloaded = false;
|
|
|
|
if (ext && strcmp(ext, ".fnl") == 0) {
|
|
if (pxl8_script_run_fennel_file(script, script->main_path) == PXL8_OK) {
|
|
reloaded = true;
|
|
}
|
|
} else if (ext && strcmp(ext, ".lua") == 0) {
|
|
if (pxl8_script_run_file(script, script->main_path) == PXL8_OK) {
|
|
reloaded = true;
|
|
}
|
|
}
|
|
|
|
if (reloaded) {
|
|
pxl8_script_restore_globals(script);
|
|
pxl8_script_call_function(script, "init");
|
|
}
|
|
|
|
return reloaded;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|