2024-10-05 21:17:22 +00:00
|
|
|
#include "twn_game_api.h"
|
|
|
|
|
2025-02-15 19:19:14 +00:00
|
|
|
/* TODO: actually move it back it its own file, it doesn't give any compilation benefits */
|
2025-02-10 15:29:35 +00:00
|
|
|
#define LUA_IMPL
|
2025-01-28 22:24:58 +00:00
|
|
|
#include "minilua.h"
|
2025-02-10 15:29:35 +00:00
|
|
|
|
|
|
|
#include "state.h"
|
|
|
|
#include "luabind.c"
|
|
|
|
|
2024-10-06 21:00:36 +00:00
|
|
|
#include <SDL2/SDL.h>
|
|
|
|
|
2025-02-15 19:19:14 +00:00
|
|
|
#define UDATA_NESTING_LIMIT 128
|
|
|
|
|
2024-10-05 21:17:22 +00:00
|
|
|
|
2024-12-23 17:59:00 +00:00
|
|
|
/* generated by bindgen.py */
|
|
|
|
void bindgen_load_twn(lua_State *L);
|
2024-12-24 07:03:19 +00:00
|
|
|
void bindgen_unload_twn(lua_State *L);
|
2025-01-11 13:01:41 +00:00
|
|
|
void bindgen_build_context(lua_State *L);
|
2025-01-13 22:35:54 +00:00
|
|
|
void bindgen_upload_context(lua_State *L);
|
2024-12-23 17:59:00 +00:00
|
|
|
|
|
|
|
|
2024-10-05 21:17:22 +00:00
|
|
|
/* require will go through physicsfs exclusively so that scripts can be in the data dir */
|
2025-01-26 08:35:34 +00:00
|
|
|
/* TODO: allow for bytecode files */
|
2025-02-02 02:00:54 +00:00
|
|
|
/* TODO: support .lua suffixes files? */
|
2024-10-05 21:17:22 +00:00
|
|
|
static int physfs_loader(lua_State *L) {
|
|
|
|
const char *name = luaL_checkstring(L, 1);
|
2025-02-02 02:00:54 +00:00
|
|
|
|
|
|
|
/* replace dots with path slashes */
|
|
|
|
char *path_copy = SDL_strdup(name);
|
|
|
|
char *ch = NULL;
|
|
|
|
while ((ch = SDL_strchr(path_copy, '.')))
|
|
|
|
*ch = '/';
|
|
|
|
|
2024-10-05 21:17:22 +00:00
|
|
|
char *final_path = NULL;
|
2025-02-02 02:00:54 +00:00
|
|
|
SDL_asprintf(&final_path, "/scripts/%s.lua", path_copy);
|
|
|
|
SDL_free(path_copy);
|
2024-10-05 21:17:22 +00:00
|
|
|
|
|
|
|
if (!file_exists(final_path)) {
|
|
|
|
char *error_message = NULL;
|
|
|
|
SDL_asprintf(&error_message, "could not find module %s in filesystem", name);
|
|
|
|
lua_pushstring(L, error_message);
|
|
|
|
free(error_message);
|
|
|
|
|
|
|
|
free(final_path);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned char *buf = NULL;
|
|
|
|
int64_t buf_size = file_to_bytes(final_path, &buf);
|
|
|
|
free(final_path);
|
|
|
|
|
2025-01-26 08:10:05 +00:00
|
|
|
/* TODO: use reader interface for streaming instead */
|
2025-02-14 18:52:06 +00:00
|
|
|
int const result = luaL_loadbuffer(L, (char *)buf, buf_size, name);
|
2024-10-05 21:17:22 +00:00
|
|
|
free(buf);
|
|
|
|
|
2025-02-14 18:52:06 +00:00
|
|
|
if (result != LUA_OK)
|
|
|
|
log_critical("%s", lua_tostring(L, -1));
|
|
|
|
|
|
|
|
return result == LUA_OK;
|
2024-10-05 21:17:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-01-11 23:44:41 +00:00
|
|
|
/* WARN! experimental and will probably be removed */
|
2025-01-12 00:20:27 +00:00
|
|
|
/* it is an attempt to ease memory usage problems posed by using lua, partially successful */
|
2025-01-11 23:44:41 +00:00
|
|
|
static void *custom_alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
|
|
|
|
(void)ud;
|
|
|
|
|
|
|
|
/* small allocations are placed in slots, as there's a big chance they will not need to be resized */
|
2025-01-12 00:20:27 +00:00
|
|
|
static char slots[1024][128];
|
2025-01-11 23:44:41 +00:00
|
|
|
static int16_t free_slots[1024] = { [0] = -1 };
|
|
|
|
static size_t free_slot_count = 1024;
|
|
|
|
|
|
|
|
if (free_slots[0] == -1)
|
|
|
|
for (int i = 0; i < 1024; i++)
|
|
|
|
free_slots[i] = (int16_t)i;
|
|
|
|
|
|
|
|
if (nsize == 0) {
|
2025-01-12 00:20:27 +00:00
|
|
|
if (ptr && (char *)ptr >= &slots[0][0] && (char *)ptr <= &slots[1024-1][128-1])
|
|
|
|
free_slots[free_slot_count++] = (int16_t)(((uintptr_t)ptr - (uintptr_t)slots) / 128);
|
|
|
|
else if (osize)
|
2025-01-11 23:44:41 +00:00
|
|
|
SDL_free(ptr);
|
|
|
|
return NULL;
|
|
|
|
} else {
|
2025-01-12 00:20:27 +00:00
|
|
|
if (!ptr && nsize <= 128 && free_slot_count > 0) {
|
2025-01-11 23:44:41 +00:00
|
|
|
/* use a slot */
|
|
|
|
return slots[free_slots[--free_slot_count]];
|
|
|
|
}
|
|
|
|
|
2025-01-12 00:20:27 +00:00
|
|
|
if ((char *)ptr >= &slots[0][0] && (char *)ptr <= &slots[1024-1][128-1]) {
|
2025-01-11 23:44:41 +00:00
|
|
|
/* still fits */
|
2025-01-12 00:20:27 +00:00
|
|
|
if (nsize <= 128)
|
2025-01-11 23:44:41 +00:00
|
|
|
return ptr;
|
|
|
|
|
|
|
|
/* move from slot to dynamic memory */
|
|
|
|
void *mem = SDL_malloc(nsize);
|
|
|
|
SDL_memcpy(mem, ptr, osize);
|
2025-01-12 00:20:27 +00:00
|
|
|
free_slots[free_slot_count++] = (int16_t)(((uintptr_t)ptr - (uintptr_t)slots) / 128);
|
2025-01-11 23:44:41 +00:00
|
|
|
return mem;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SDL_realloc(ptr, nsize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-15 19:19:14 +00:00
|
|
|
static void exchange_lua_states(lua_State *from, lua_State *to, int level, int index) {
|
|
|
|
if (level >= UDATA_NESTING_LIMIT) {
|
|
|
|
log_critical("ctx.udata nesting limit is reached (%u)", UDATA_NESTING_LIMIT);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* TODO: use arrays for optimized paths */
|
|
|
|
/* TODO: preallocate table records */
|
|
|
|
switch (lua_type(from, index)) {
|
|
|
|
case LUA_TTABLE:
|
|
|
|
lua_newtable(to);
|
|
|
|
lua_pushnil(from); /* first key */
|
|
|
|
while (lua_next(from, index - 1) != 0) {
|
|
|
|
/* 'key' at index -2 and 'value' at index -1 */
|
|
|
|
exchange_lua_states(from, to, level + 1, -2);
|
|
|
|
exchange_lua_states(from, to, level + 1, -1);
|
|
|
|
lua_settable(to, index - 2);
|
|
|
|
/* removes 'value'; keeps 'key' for next iteration */
|
|
|
|
lua_pop(from, 1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case LUA_TNUMBER:
|
|
|
|
lua_pushnumber(to, lua_tonumber(from, index));
|
|
|
|
break;
|
|
|
|
case LUA_TBOOLEAN:
|
|
|
|
lua_pushboolean(to, lua_toboolean(from, index));
|
|
|
|
break;
|
|
|
|
case LUA_TSTRING:
|
|
|
|
lua_pushstring(to, lua_tostring(from, index));
|
|
|
|
break;
|
|
|
|
case LUA_TNIL:
|
|
|
|
lua_pushnil(to);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* TODO: provide a path and type of it for better diagnostic */
|
|
|
|
log_warn("Unserializable udata found and is ignored");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-10-05 21:17:22 +00:00
|
|
|
void game_tick(void) {
|
|
|
|
if (ctx.initialization_needed) {
|
|
|
|
if (!ctx.udata)
|
2025-01-31 02:11:10 +00:00
|
|
|
ctx.udata = calloc(1, sizeof (State));
|
2024-10-05 21:17:22 +00:00
|
|
|
|
|
|
|
State *state = ctx.udata;
|
|
|
|
|
|
|
|
/* let's init lua */
|
2025-02-15 19:19:14 +00:00
|
|
|
lua_State *new_state = luaL_newstate();
|
|
|
|
lua_setallocf(new_state, custom_alloc, NULL);
|
|
|
|
|
|
|
|
/* state existed already, copy its udata over */
|
2024-10-05 21:17:22 +00:00
|
|
|
if (state->L != NULL) {
|
2025-02-15 19:19:14 +00:00
|
|
|
lua_getglobal(state->L, "ctx");
|
|
|
|
lua_getfield(state->L, -1, "udata");
|
|
|
|
SDL_assert(!lua_isnoneornil(state->L, -1));
|
|
|
|
SDL_assert(!lua_isnoneornil(state->L, -2));
|
|
|
|
// SDL_TriggerBreakpoint();
|
|
|
|
if (!lua_isnoneornil(state->L, -1)) {
|
|
|
|
log_info("Exchanging lua states...");
|
|
|
|
lua_newtable(new_state);
|
|
|
|
exchange_lua_states(state->L, new_state, 0, -1);
|
|
|
|
lua_setfield(new_state, -2, "udata");
|
|
|
|
lua_setglobal(new_state, "ctx");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* bye :) */
|
2024-10-05 21:17:22 +00:00
|
|
|
lua_close(state->L);
|
|
|
|
}
|
|
|
|
|
2025-02-15 19:19:14 +00:00
|
|
|
state->L = new_state;
|
2025-01-11 23:44:41 +00:00
|
|
|
|
2024-12-23 17:59:00 +00:00
|
|
|
/* fakey version of luaL_openlibs() that excludes file i/o and os stuff */
|
2024-10-05 21:17:22 +00:00
|
|
|
{
|
|
|
|
static const luaL_Reg loaded_libs[] = {
|
|
|
|
{ LUA_GNAME, luaopen_base },
|
|
|
|
{ LUA_LOADLIBNAME, luaopen_package },
|
|
|
|
{ LUA_COLIBNAME, luaopen_coroutine },
|
|
|
|
{ LUA_TABLIBNAME, luaopen_table },
|
|
|
|
{ LUA_STRLIBNAME, luaopen_string },
|
|
|
|
{ LUA_MATHLIBNAME, luaopen_math },
|
|
|
|
{ LUA_UTF8LIBNAME, luaopen_utf8 },
|
|
|
|
{ LUA_DBLIBNAME, luaopen_debug },
|
|
|
|
{ NULL, NULL },
|
|
|
|
};
|
|
|
|
|
|
|
|
for (const luaL_Reg *lib = loaded_libs; lib->func; ++lib) {
|
|
|
|
luaL_requiref(state->L, lib->name, lib->func, true);
|
|
|
|
lua_pop(state->L, 1); /* already stored, don't need the copy */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* package.searchers = { physfs_loader } */
|
|
|
|
lua_getglobal(state->L, "package");
|
2025-01-11 23:44:41 +00:00
|
|
|
lua_createtable(state->L, 0, 1);
|
2024-10-05 21:17:22 +00:00
|
|
|
lua_setfield(state->L, -2, "searchers");
|
|
|
|
|
|
|
|
lua_getfield(state->L, -1, "searchers");
|
|
|
|
lua_pushcfunction(state->L, physfs_loader);
|
|
|
|
lua_seti(state->L, -2, 1);
|
|
|
|
|
|
|
|
/* pop package, package.searchers */
|
|
|
|
lua_pop(state->L, 2);
|
|
|
|
|
2024-10-05 21:40:39 +00:00
|
|
|
/* binding */
|
2024-12-23 17:59:00 +00:00
|
|
|
bindgen_load_twn(state->L);
|
2024-10-05 21:40:39 +00:00
|
|
|
|
2024-10-05 21:17:22 +00:00
|
|
|
/* now finally get to running the code */
|
2024-10-07 03:37:49 +00:00
|
|
|
unsigned char *game_buf = NULL;
|
|
|
|
size_t game_buf_size = file_to_bytes("/scripts/game.lua", &game_buf);
|
2025-01-26 08:10:05 +00:00
|
|
|
/* TODO: use reader interface for streaming instead */
|
2024-10-07 03:37:49 +00:00
|
|
|
if (luaL_loadbuffer(state->L, (char *)game_buf, game_buf_size, "game.lua") == LUA_OK) {
|
2024-10-05 21:17:22 +00:00
|
|
|
if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) {
|
|
|
|
log_critical("%s", lua_tostring(state->L, -1));
|
|
|
|
lua_pop(state->L, 1);
|
2025-01-26 05:48:59 +00:00
|
|
|
} else
|
|
|
|
state->loaded_successfully = true;
|
|
|
|
} else {
|
|
|
|
/* got some sort of error, it should be pushed on top of the stack */
|
|
|
|
SDL_assert(lua_isstring(state->L, -1));
|
|
|
|
log_critical("Error loading /scripts/game.lua entry: %s", luaL_tolstring(state->L, -1, NULL));
|
|
|
|
lua_pop(state->L, 1);
|
2024-10-05 21:17:22 +00:00
|
|
|
}
|
2025-01-26 05:48:59 +00:00
|
|
|
|
2024-10-07 03:37:49 +00:00
|
|
|
free(game_buf);
|
2025-01-26 05:48:59 +00:00
|
|
|
|
2024-10-05 21:17:22 +00:00
|
|
|
/* from this point we have access to everything defined in lua */
|
|
|
|
}
|
|
|
|
|
|
|
|
State *state = ctx.udata;
|
|
|
|
|
2025-01-26 05:48:59 +00:00
|
|
|
if (state->loaded_successfully) {
|
|
|
|
bindgen_build_context(state->L);
|
|
|
|
lua_getglobal(state->L, "ctx");
|
|
|
|
if (!lua_isnoneornil(state->L, -1)) {
|
|
|
|
lua_getfield(state->L, -1, "udata");
|
|
|
|
lua_setfield(state->L, -3, "udata");
|
|
|
|
}
|
2024-10-05 21:17:22 +00:00
|
|
|
lua_pop(state->L, 1);
|
2025-01-26 05:48:59 +00:00
|
|
|
lua_setglobal(state->L, "ctx");
|
2025-01-13 22:35:54 +00:00
|
|
|
|
2025-01-26 05:48:59 +00:00
|
|
|
lua_getglobal(state->L, "game_tick");
|
|
|
|
if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) {
|
|
|
|
log_critical("%s", lua_tostring(state->L, -1));
|
|
|
|
lua_pop(state->L, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
lua_getglobal(state->L, "ctx");
|
|
|
|
bindgen_upload_context(state->L);
|
|
|
|
}
|
2024-10-05 21:17:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void game_end(void) {
|
|
|
|
State *state = ctx.udata;
|
2024-12-24 07:03:19 +00:00
|
|
|
bindgen_unload_twn(state->L);
|
2024-10-05 21:17:22 +00:00
|
|
|
lua_close(state->L);
|
|
|
|
free(state);
|
|
|
|
}
|