Files
basic1/emulator/lua_game_emulator.cpp
Adolfo Reyna 2a472fc29f Add frame tick system for continuous animation
- 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
2026-02-07 13:20:10 -05:00

204 lines
5.3 KiB
C++

// ============================================================================
// LUA GAME WRAPPER - EMULATOR IMPLEMENTATION
// ============================================================================
// Manages Lua VM lifecycle and script execution for desktop emulator
#include "../games/lua_game.h"
#include "../games/lua_bindings.h"
#include <stdio.h>
#include <string.h>
#include <fstream>
#include <sstream>
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() {
// Open Lua script from filesystem (emulator)
std::ifstream file(script_path);
if (!file.is_open()) {
error_message = "Failed to open file: " + script_path;
return false;
}
// Read entire file into string
std::stringstream buffer;
buffer << file.rdbuf();
std::string script_content = buffer.str();
file.close();
if (script_content.empty()) {
error_message = "Script file is empty";
return false;
}
if (script_content.size() > 64 * 1024) { // Limit to 64KB
error_message = "Script file too large (> 64KB)";
return false;
}
// Load script into Lua
int result = luaL_loadbuffer(L, script_content.c_str(), script_content.size(), script_path.c_str());
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 registry for frame updates flag
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
}