- Added INPUT_FRAME_TICK event type to input_event.h - Added wants_frame_updates() virtual method to Game base class - Implemented frame tick logic in main loop (basic1.cpp and emulator/main.cpp) - Added Lua bindings: game.set_frame_updates(bool) and INPUT.FRAME_TICK - Updated LuaGame to support frame updates via registry flag - Updated ball.lua to use continuous frame updates for smooth animation - Both hardware and emulator now support continuous animation for physics/games
227 lines
5.8 KiB
C++
227 lines
5.8 KiB
C++
// ============================================================================
|
|
// LUA GAME WRAPPER - IMPLEMENTATION
|
|
// ============================================================================
|
|
// Manages Lua VM lifecycle and script execution
|
|
|
|
#include "lua_game.h"
|
|
#include "lua_bindings.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
extern "C" {
|
|
#include "ff.h" // FatFS for SD card access
|
|
}
|
|
|
|
LuaGame::LuaGame(const char* script_path, uint16_t width, uint16_t height,
|
|
LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager)
|
|
: Game(width, height, renderer, gui, input_manager),
|
|
L(nullptr),
|
|
script_path(script_path),
|
|
loaded(false) {
|
|
|
|
// Create new Lua state
|
|
L = luaL_newstate();
|
|
if (!L) {
|
|
error_message = "Failed to create Lua state";
|
|
printf("LuaGame: %s\n", error_message.c_str());
|
|
return;
|
|
}
|
|
|
|
// Open standard Lua libraries (math, string, table, coroutine)
|
|
luaL_openlibs(L);
|
|
|
|
// Register game API bindings
|
|
lua_bindings_register(L, this);
|
|
|
|
// Load the script
|
|
loaded = load_script();
|
|
|
|
if (!loaded) {
|
|
printf("LuaGame: Failed to load %s: %s\n", script_path, error_message.c_str());
|
|
} else {
|
|
printf("LuaGame: Successfully loaded %s\n", script_path);
|
|
}
|
|
}
|
|
|
|
LuaGame::~LuaGame() {
|
|
if (L) {
|
|
lua_close(L);
|
|
L = nullptr;
|
|
}
|
|
}
|
|
|
|
bool LuaGame::load_script() {
|
|
FIL fil;
|
|
FRESULT fr;
|
|
|
|
// Open Lua script from SD card
|
|
fr = f_open(&fil, script_path.c_str(), FA_READ);
|
|
if (fr != FR_OK) {
|
|
error_message = "Failed to open file (FatFS error: ";
|
|
error_message += std::to_string((int)fr);
|
|
error_message += ")";
|
|
return false;
|
|
}
|
|
|
|
// Get file size
|
|
FSIZE_t file_size = f_size(&fil);
|
|
if (file_size == 0 || file_size > 64 * 1024) { // Limit to 64KB
|
|
f_close(&fil);
|
|
error_message = "Script file size invalid (0 or > 64KB)";
|
|
return false;
|
|
}
|
|
|
|
// Allocate buffer for script
|
|
char* script_buffer = new char[file_size + 1];
|
|
if (!script_buffer) {
|
|
f_close(&fil);
|
|
error_message = "Failed to allocate memory for script";
|
|
return false;
|
|
}
|
|
|
|
// Read script into buffer
|
|
UINT bytes_read;
|
|
fr = f_read(&fil, script_buffer, file_size, &bytes_read);
|
|
f_close(&fil);
|
|
|
|
if (fr != FR_OK || bytes_read != file_size) {
|
|
delete[] script_buffer;
|
|
error_message = "Failed to read script file";
|
|
return false;
|
|
}
|
|
|
|
script_buffer[file_size] = '\0';
|
|
|
|
// Load script into Lua
|
|
int result = luaL_loadbuffer(L, script_buffer, file_size, script_path.c_str());
|
|
delete[] script_buffer;
|
|
|
|
if (result != LUA_OK) {
|
|
report_error("load script");
|
|
return false;
|
|
}
|
|
|
|
// Execute script (loads functions into global namespace)
|
|
result = lua_pcall(L, 0, 0, 0);
|
|
if (result != LUA_OK) {
|
|
report_error("execute script");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void LuaGame::init() {
|
|
if (!loaded) return;
|
|
|
|
// Call Lua init() function if it exists
|
|
lua_getglobal(L, "init");
|
|
if (lua_isfunction(L, -1)) {
|
|
call_lua_function("init", 0, 0);
|
|
} else {
|
|
lua_pop(L, 1); // Pop non-function value
|
|
printf("LuaGame: Warning - no init() function found\n");
|
|
}
|
|
}
|
|
|
|
bool LuaGame::update(const InputEvent& event) {
|
|
if (!loaded) return false;
|
|
|
|
// Call Lua update(event) function if it exists
|
|
lua_getglobal(L, "update");
|
|
if (!lua_isfunction(L, -1)) {
|
|
lua_pop(L, 1);
|
|
return false; // No update function, no redraw needed
|
|
}
|
|
|
|
// Push event table to Lua
|
|
lua_newtable(L);
|
|
|
|
lua_pushstring(L, "type");
|
|
lua_pushinteger(L, (int)event.type);
|
|
lua_settable(L, -3);
|
|
|
|
lua_pushstring(L, "x");
|
|
lua_pushinteger(L, event.x);
|
|
lua_settable(L, -3);
|
|
|
|
lua_pushstring(L, "y");
|
|
lua_pushinteger(L, event.y);
|
|
lua_settable(L, -3);
|
|
|
|
lua_pushstring(L, "button_id");
|
|
lua_pushinteger(L, event.button_id);
|
|
lua_settable(L, -3);
|
|
|
|
lua_pushstring(L, "valid");
|
|
lua_pushboolean(L, event.valid);
|
|
lua_settable(L, -3);
|
|
|
|
// Call update(event) with 1 arg, expecting 1 result (needs_redraw)
|
|
if (!call_lua_function("update", 1, 1)) {
|
|
return false;
|
|
}
|
|
|
|
// Get return value (needs redraw?)
|
|
bool needs_redraw = lua_toboolean(L, -1);
|
|
lua_pop(L, 1);
|
|
|
|
return needs_redraw;
|
|
}
|
|
|
|
void LuaGame::draw() {
|
|
if (!loaded) return;
|
|
|
|
// Call Lua draw() function if it exists
|
|
lua_getglobal(L, "draw");
|
|
if (lua_isfunction(L, -1)) {
|
|
call_lua_function("draw", 0, 0);
|
|
} else {
|
|
lua_pop(L, 1);
|
|
}
|
|
}
|
|
|
|
bool LuaGame::wants_to_exit() const {
|
|
if (!L) return false;
|
|
|
|
// Check if Lua script requested exit
|
|
lua_pushstring(L, "__exit_requested");
|
|
lua_gettable(L, LUA_REGISTRYINDEX);
|
|
bool exit = lua_toboolean(L, -1);
|
|
lua_pop(L, 1);
|
|
|
|
return exit;
|
|
}
|
|
|
|
bool LuaGame::wants_frame_updates() const {
|
|
if (!L) return false;
|
|
|
|
// Check if Lua script wants continuous frame updates
|
|
lua_pushstring(L, "__wants_frame_updates");
|
|
lua_gettable(L, LUA_REGISTRYINDEX);
|
|
bool wants_updates = lua_toboolean(L, -1);
|
|
lua_pop(L, 1);
|
|
|
|
return wants_updates;
|
|
}
|
|
|
|
bool LuaGame::call_lua_function(const char* func_name, int nargs, int nresults) {
|
|
int result = lua_pcall(L, nargs, nresults, 0);
|
|
if (result != LUA_OK) {
|
|
report_error(func_name);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void LuaGame::report_error(const char* context) {
|
|
const char* msg = lua_tostring(L, -1);
|
|
if (msg) {
|
|
error_message = context;
|
|
error_message += ": ";
|
|
error_message += msg;
|
|
printf("LuaGame Error [%s]: %s\n", context, msg);
|
|
}
|
|
lua_pop(L, 1); // Pop error message
|
|
}
|