#include "twn_game_api.h" /* TODO: actually move it back it its own file, it doesn't give any compilation benefits */ #define LUA_IMPL #include "minilua.h" #include "state.h" #include "luabind.c" #include #define UDATA_NESTING_LIMIT 128 /* generated by bindgen.py */ void bindgen_load_twn(lua_State *L); void bindgen_unload_twn(lua_State *L); void bindgen_build_context(lua_State *L); void bindgen_upload_context(lua_State *L); /* require will go through physicsfs exclusively so that scripts can be in the data dir */ /* TODO: allow for bytecode files */ /* TODO: support .lua suffixes files? */ static int physfs_loader(lua_State *L) { const char *name = luaL_checkstring(L, 1); /* replace dots with path slashes */ char *path_copy = SDL_strdup(name); char *ch = NULL; while ((ch = SDL_strchr(path_copy, '.'))) *ch = '/'; char *final_path = NULL; SDL_asprintf(&final_path, "/scripts/%s.lua", path_copy); SDL_free(path_copy); 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); /* TODO: use reader interface for streaming instead */ int const result = luaL_loadbuffer(L, (char *)buf, buf_size, name); free(buf); if (result != LUA_OK) log_critical("%s", lua_tostring(L, -1)); return result == LUA_OK; } /* WARN! experimental and will probably be removed */ /* it is an attempt to ease memory usage problems posed by using lua, partially successful */ 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 */ static char slots[1024][128]; 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) { 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) SDL_free(ptr); return NULL; } else { if (!ptr && nsize <= 128 && free_slot_count > 0) { /* use a slot */ return slots[free_slots[--free_slot_count]]; } if ((char *)ptr >= &slots[0][0] && (char *)ptr <= &slots[1024-1][128-1]) { /* still fits */ if (nsize <= 128) return ptr; /* move from slot to dynamic memory */ void *mem = SDL_malloc(nsize); SDL_memcpy(mem, ptr, osize); free_slots[free_slot_count++] = (int16_t)(((uintptr_t)ptr - (uintptr_t)slots) / 128); return mem; } return SDL_realloc(ptr, nsize); } } 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; } } void game_tick(void) { if (ctx.initialization_needed) { if (!ctx.udata) ctx.udata = calloc(1, sizeof (State)); State *state = ctx.udata; /* let's init lua */ lua_State *new_state = luaL_newstate(); lua_setallocf(new_state, custom_alloc, NULL); /* state existed already, copy its udata over */ if (state->L != NULL) { 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 :) */ lua_close(state->L); } state->L = new_state; /* fakey version of luaL_openlibs() that excludes file i/o and os stuff */ { 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"); lua_createtable(state->L, 0, 1); 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); /* binding */ bindgen_load_twn(state->L); /* now finally get to running the code */ unsigned char *game_buf = NULL; size_t game_buf_size = file_to_bytes("/scripts/game.lua", &game_buf); /* TODO: use reader interface for streaming instead */ if (luaL_loadbuffer(state->L, (char *)game_buf, game_buf_size, "game.lua") == LUA_OK) { if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) { log_critical("%s", lua_tostring(state->L, -1)); lua_pop(state->L, 1); } 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); } free(game_buf); /* from this point we have access to everything defined in lua */ } State *state = ctx.udata; 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"); } lua_pop(state->L, 1); lua_setglobal(state->L, "ctx"); 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); } } void game_end(void) { State *state = ctx.udata; bindgen_unload_twn(state->L); lua_close(state->L); free(state); }