diff --git a/include/lua.hpp b/include/lua.hpp index ceb91cae0..eebc15f7b 100644 --- a/include/lua.hpp +++ b/include/lua.hpp @@ -2,6 +2,13 @@ #include "helpers.hpp" #include "memory.hpp" +// The kinds of events that can cause a Lua call. +// Frame: Call program on frame end +// TODO: Add more +enum class LuaEvent { + Frame, +}; + #ifdef PANDA3DS_ENABLE_LUA extern "C" { #include @@ -14,6 +21,9 @@ extern "C" { class LuaManager { lua_State* L = nullptr; bool initialized = false; + bool haveScript = false; + + void signalEventInternal(LuaEvent e); public: // For Lua we must have some global pointers to our emulator objects to use them in script code via thunks. See the thunks in lua.cpp as an @@ -27,6 +37,11 @@ class LuaManager { void initializeThunks(); void loadFile(const char* path); void reset(); + void signalEvent(LuaEvent e) { + if (haveScript) [[unlikely]] { + signalEventInternal(e); + } + } }; #elif // Lua not enabled, Lua manager does nothing @@ -38,5 +53,6 @@ class LuaManager { void initialize() {} void loadFile(const char* path) {} void reset() {} + void signalEvent(LuaEvent e) {} }; #endif \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index c21e86b9f..ba9b06237 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -82,7 +82,6 @@ Emulator::Emulator() } } - lua.initialize(); reset(ReloadOption::NoReload); } @@ -414,6 +413,7 @@ void Emulator::runFrame() { if (running) { cpu.runFrame(); // Run 1 frame of instructions gpu.display(); // Display graphics + lua.signalEvent(LuaEvent::Frame); // Send VBlank interrupts ServiceManager& srv = kernel.getServiceManager(); diff --git a/src/lua.cpp b/src/lua.cpp index 1cdcc6895..bcaac3ab5 100644 --- a/src/lua.cpp +++ b/src/lua.cpp @@ -14,27 +14,54 @@ void LuaManager::initialize() { initializeThunks(); initialized = true; + haveScript = false; } void LuaManager::close() { if (initialized) { lua_close(L); initialized = false; + haveScript = false; L = nullptr; } } void LuaManager::loadFile(const char* path) { + // Initialize Lua if it has not been initialized + if (!initialized) { + initialize(); + } + + // If init failed, don't execute + if (!initialized) { + printf("Lua initialization failed, file won't run\n"); + haveScript = false; + + return; + } + int status = luaL_loadfile(L, path); // load Lua script int ret = lua_pcall(L, 0, 0, 0); // tell Lua to run the script if (ret != 0) { + haveScript = false; fprintf(stderr, "%s\n", lua_tostring(L, -1)); // tell us what mistake we made + } else { + haveScript = true; } } +void LuaManager::signalEventInternal(LuaEvent e) { + lua_getglobal(L, "eventHandler"); // We want to call the event handler + lua_pushnumber(L, static_cast(e)); // Push event type + + // Call the function with 1 argument and 0 outputs, without an error handler + lua_pcall(L, 1, 0, 0); +} + void LuaManager::reset() { // Reset scripts + haveScript = false; } // Initialize C++ thunks for Lua code to call here @@ -62,15 +89,53 @@ MAKE_MEMORY_FUNCTIONS(32) MAKE_MEMORY_FUNCTIONS(64) #undef MAKE_MEMORY_FUNCTIONS +// clang-format off +static constexpr luaL_Reg functions[] = { + { "__read8", read8Thunk }, + { "__read16", read16Thunk }, + { "__read32", read32Thunk }, + { "__read64", read64Thunk }, + { "__write8", write8Thunk} , + { "__write16", write16Thunk }, + { "__write32", write32Thunk }, + { "__write64", write64Thunk }, + { nullptr, nullptr }, +}; +// clang-format on + void LuaManager::initializeThunks() { - lua_register(L, "read8", read8Thunk); - lua_register(L, "read16", read16Thunk); - lua_register(L, "read32", read32Thunk); - lua_register(L, "read64", read64Thunk); - lua_register(L, "write8", write8Thunk); - lua_register(L, "write16", write16Thunk); - lua_register(L, "write32", write32Thunk); - lua_register(L, "write64", write64Thunk); + static const char* runtimeInit = R"( + Pand = { + read8 = function(addr) return GLOBALS.__read8(addr) end, + read16 = function(addr) return GLOBALS.__read16(addr) end, + read32 = function(addr) return GLOBALS.__read32(addr) end, + read64 = function(addr) return GLOBALS.__read64(addr) end, + write8 = function(addr, value) GLOBALS.__write8(addr, value) end, + write16 = function(addr, value) GLOBALS.__write16(addr, value) end, + write32 = function(addr, value) GLOBALS.__write32(addr, value) end, + write64 = function(addr, value) GLOBALS.__write64(addr, value) end, + Frame = __Frame, + } +)"; + + auto addIntConstant = [&](T x, const char* name) { + lua_pushinteger(L, (int)x); + lua_setglobal(L, name); + }; + + luaL_register(L, "GLOBALS", functions); + addIntConstant(LuaEvent::Frame, "__Frame"); + + // Call our Lua runtime initialization before any Lua script runs + luaL_loadstring(L, runtimeInit); + int ret = lua_pcall(L, 0, 0, 0); // tell Lua to run the script + + if (ret != 0) { + initialized = false; + fprintf(stderr, "%s\n", lua_tostring(L, -1)); // Init should never fail! + } else { + initialized = true; + } } #endif \ No newline at end of file