From 1149da9f6a8f3f0cf4e72714ac17db89b5e6e78d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Gallet?= Date: Sun, 5 Feb 2023 15:46:03 +0100 Subject: [PATCH] Manage lua scripts and change the callback system, to prepare for #519 --- CHANGELOG.md | 1 + docs/guides/lua.md | 33 ++++++++++- src/program/GameLoop.cpp | 13 +++-- src/program/Makefile.am | 3 + src/program/lua/Callbacks.cpp | 86 ++++++++++++++++++++++++++++ src/program/lua/Callbacks.h | 50 ++++++++++++++++ src/program/lua/LuaFunctionList.cpp | 48 ++++++++++++++++ src/program/lua/LuaFunctionList.h | 54 +++++++++++++++++ src/program/lua/Main.cpp | 39 ++++++++++++- src/program/lua/Main.h | 3 + src/program/lua/NamedLuaFunction.cpp | 65 +++++++++++++++++++++ src/program/lua/NamedLuaFunction.h | 59 +++++++++++++++++++ 12 files changed, 445 insertions(+), 9 deletions(-) create mode 100644 src/program/lua/Callbacks.cpp create mode 100644 src/program/lua/Callbacks.h create mode 100644 src/program/lua/LuaFunctionList.cpp create mode 100644 src/program/lua/LuaFunctionList.h create mode 100644 src/program/lua/NamedLuaFunction.cpp create mode 100644 src/program/lua/NamedLuaFunction.h diff --git a/CHANGELOG.md b/CHANGELOG.md index e803e95c..f15f0ee0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Implement XIQueryDevice() * Implement SDL_JoystickGetDeviceInstanceID() * Add a window for lua output +* Manage lua scripts and change the callback system ### Changed diff --git a/docs/guides/lua.md b/docs/guides/lua.md index 18386561..27669969 100644 --- a/docs/guides/lua.md +++ b/docs/guides/lua.md @@ -261,8 +261,37 @@ Returns 1 of the current frame is a draw frame, or 0 if not. ### Callbacks -These functions, if defined in the lua script, are called at specific moments -during the game execution. +#### callback.onStartup + + None callback.onStartup(Function f) + +Registers `f` to be called when the game has started, before the end of the first frame. + +#### callback.onInput + + None callback.onInput(Function f) + +Registers `f` to be called when input for the current frame is decided. Change the movie input if +in recording mode. Not called when movie is in playback mode. + +#### callback.onFrame + + None callback.onFrame(Function f) + +Registers `f` to be called after frame has completed. + +#### callback.onPaint + + None callback.onPaint(Function f) + +Registers `f` to be called at the end of the frame before the screen rendering +is performed. All Gui functions must be executed inside this callback. To +include Lua draws in encodes, `Video > OSD > OSD on video encode` can be checked. + +### Callbacks (obsolete) + +The old callback method consists on defining functions with specific names above, +which will be called at specific moments during the game execution. #### onStartup diff --git a/src/program/GameLoop.cpp b/src/program/GameLoop.cpp index 0f877102..9add6de3 100644 --- a/src/program/GameLoop.cpp +++ b/src/program/GameLoop.cpp @@ -36,7 +36,8 @@ // #include "SaveState.h" #include "SaveStateList.h" #include "lua/Input.h" -#include "lua/Main.h" +#include "lua/Callbacks.h" +#include "lua/NamedLuaFunction.h" #include "ramsearch/MemAccess.h" #include "../shared/sockethelpers.h" @@ -70,7 +71,7 @@ void GameLoop::start() init(); initProcessMessages(); - Lua::Main::callLua("onStartup"); + Lua::Callbacks::call(Lua::NamedLuaFunction::CallbackStartup); while (1) { @@ -140,7 +141,7 @@ void GameLoop::start() } } - Lua::Main::callLua("onFrame"); + Lua::Callbacks::call(Lua::NamedLuaFunction::CallbackFrame); endFrameMessages(ai); @@ -526,7 +527,7 @@ bool GameLoop::startFrameMessages() } /* Execute the lua callback onPaint here */ - Lua::Main::callLua("onPaint"); + Lua::Callbacks::call(Lua::NamedLuaFunction::CallbackPaint); sendMessage(MSGN_START_FRAMEBOUNDARY); @@ -611,7 +612,7 @@ void GameLoop::processInputs(AllInputs &ai) /* Call lua onInput() here so that a script can modify inputs */ Lua::Input::registerInputs(&ai); - Lua::Main::callLua("onInput"); + Lua::Callbacks::call(Lua::NamedLuaFunction::CallbackInput); if (context->config.sc.recording == SharedConfig::RECORDING_WRITE) { /* If the input editor is visible, we should keep future inputs. @@ -724,7 +725,7 @@ void GameLoop::processInputs(AllInputs &ai) /* Call lua onInput() here so that a script can modify inputs */ Lua::Input::registerInputs(&ai); - Lua::Main::callLua("onInput"); + Lua::Callbacks::call(Lua::NamedLuaFunction::CallbackInput); /* Update controller inputs if controller window is shown */ emit showControllerInputs(ai); diff --git a/src/program/Makefile.am b/src/program/Makefile.am index 2344cc1b..93024fa6 100644 --- a/src/program/Makefile.am +++ b/src/program/Makefile.am @@ -53,7 +53,10 @@ libTAS_SOURCES = \ SaveState.cpp \ SaveStateList.cpp \ utils.cpp \ + lua/Callbacks.cpp \ lua/Gui.cpp \ + lua/LuaFunctionList.cpp \ + lua/NamedLuaFunction.cpp \ lua/Input.cpp \ lua/Main.cpp \ lua/Memory.cpp \ diff --git a/src/program/lua/Callbacks.cpp b/src/program/lua/Callbacks.cpp new file mode 100644 index 00000000..20ee3a48 --- /dev/null +++ b/src/program/lua/Callbacks.cpp @@ -0,0 +1,86 @@ +/* + Copyright 2015-2020 Clément Gallet + + This file is part of libTAS. + + libTAS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libTAS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libTAS. If not, see . + */ + +#include "Callbacks.h" +#include "Main.h" +#include "../Context.h" +#include "NamedLuaFunction.h" +#include "LuaFunctionList.h" + +#include +extern "C" { +#include +#include +} + +namespace Lua { + +static LuaFunctionList lfl; + +/* List of functions to register */ +static const luaL_Reg callback_functions[] = +{ + { "onStartup", Callbacks::onStartup}, + { "onInput", Callbacks::onInput}, + { "onFrame", Callbacks::onFrame}, + { "onPaint", Callbacks::onPaint}, + { NULL, NULL } +}; + +void Callbacks::registerFunctions(lua_State *L) +{ + luaL_newlib(L, callback_functions); + lua_setglobal(L, "callback"); +} + +void Callbacks::clear() +{ + lfl.clear(); +} + +int Callbacks::onStartup(lua_State *L) +{ + lfl.add(L, NamedLuaFunction::CallbackStartup); + return 0; +} + +int Callbacks::onInput(lua_State *L) +{ + lfl.add(L, NamedLuaFunction::CallbackInput); + return 0; +} + +int Callbacks::onFrame(lua_State *L) +{ + lfl.add(L, NamedLuaFunction::CallbackFrame); + return 0; +} + +int Callbacks::onPaint(lua_State *L) +{ + lfl.add(L, NamedLuaFunction::CallbackPaint); + return 0; +} + +void Callbacks::call(NamedLuaFunction::CallbackType type) +{ + lfl.call(type); +} + +} diff --git a/src/program/lua/Callbacks.h b/src/program/lua/Callbacks.h new file mode 100644 index 00000000..feb504bc --- /dev/null +++ b/src/program/lua/Callbacks.h @@ -0,0 +1,50 @@ +/* + Copyright 2015-2020 Clément Gallet + + This file is part of libTAS. + + libTAS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libTAS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libTAS. If not, see . + */ + +#ifndef LIBTAS_LUACALLBACKS_H_INCLUDED +#define LIBTAS_LUACALLBACKS_H_INCLUDED + +#include "NamedLuaFunction.h" + +extern "C" { +#include +} + +namespace Lua { + +namespace Callbacks { + + /* Register all functions */ + void registerFunctions(lua_State *L); + + void clear(); + + int onStartup(lua_State *L); + + int onInput(lua_State *L); + + int onFrame(lua_State *L); + + int onPaint(lua_State *L); + + void call(NamedLuaFunction::CallbackType type); +} +} + +#endif diff --git a/src/program/lua/LuaFunctionList.cpp b/src/program/lua/LuaFunctionList.cpp new file mode 100644 index 00000000..adbfb5b5 --- /dev/null +++ b/src/program/lua/LuaFunctionList.cpp @@ -0,0 +1,48 @@ +/* + Copyright 2015-2020 Clément Gallet + + This file is part of libTAS. + + libTAS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libTAS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libTAS. If not, see . + */ + +#include "LuaFunctionList.h" + +namespace Lua { + +void LuaFunctionList::add(lua_State *L, NamedLuaFunction::CallbackType t) +{ + functions.emplace_back(L, t); +} + +void LuaFunctionList::removeForFile(const std::string& file) +{ + functions.remove_if([&file](const NamedLuaFunction& nlf){ return 0 == file.compare(nlf.file); }); +} + +void LuaFunctionList::call(NamedLuaFunction::CallbackType c) +{ + for (auto& nlf : functions) { + if (nlf.type == c) { + nlf.call(); + } + } +} + +void LuaFunctionList::clear() +{ + functions.clear(); +} + +} diff --git a/src/program/lua/LuaFunctionList.h b/src/program/lua/LuaFunctionList.h new file mode 100644 index 00000000..731e93dd --- /dev/null +++ b/src/program/lua/LuaFunctionList.h @@ -0,0 +1,54 @@ +/* + Copyright 2015-2020 Clément Gallet + + This file is part of libTAS. + + libTAS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libTAS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libTAS. If not, see . + */ + +#ifndef LIBTAS_LUAFUNCTIONLIST_H_INCLUDED +#define LIBTAS_LUAFUNCTIONLIST_H_INCLUDED + +#include "NamedLuaFunction.h" + +extern "C" { +#include +} +#include +#include + +namespace Lua { + +class LuaFunctionList { + +public: + /* Add a named function from lua stack */ + void add(lua_State *L, NamedLuaFunction::CallbackType t); + + /* Remove all callbacks from a file */ + void removeForFile(const std::string& file); + + /* Call all callbacks from a type */ + void call(NamedLuaFunction::CallbackType c); + + /* Clear all callbacks */ + void clear(); + +private: + std::list functions; + +}; +} + +#endif diff --git a/src/program/lua/Main.cpp b/src/program/lua/Main.cpp index c3a206d2..061328c2 100644 --- a/src/program/lua/Main.cpp +++ b/src/program/lua/Main.cpp @@ -23,6 +23,7 @@ #include "Movie.h" #include "Memory.h" #include "Print.h" +#include "Callbacks.h" #include extern "C" { @@ -47,19 +48,24 @@ void Lua::Main::init(Context* context) Lua::Input::registerFunctions(lua_state); Lua::Memory::registerFunctions(lua_state); Lua::Movie::registerFunctions(lua_state, context); - + Lua::Callbacks::registerFunctions(lua_state); Lua::Print::init(lua_state); } void Lua::Main::exit() { + Lua::Callbacks::clear(); + if (lua_state) lua_close(lua_state); lua_state = nullptr; } +std::string luaFile; + void Lua::Main::run(std::string filename) { + luaFile = filename; int status = luaL_dofile(lua_state, filename.c_str()); if (status != 0) { std::cerr << "Error " << status << " loading lua script " << filename << std::endl; @@ -68,6 +74,37 @@ void Lua::Main::run(std::string filename) else { std::cout << "Loaded script " << filename << std::endl; } + + /* Push old-style callback methods into new-style */ + lua_getglobal(lua_state, "onStartup"); + if (lua_isfunction(lua_state, -1)) + Lua::Callbacks::onStartup(lua_state); + else + lua_pop(lua_state, 1); + + lua_getglobal(lua_state, "onInput"); + if (lua_isfunction(lua_state, -1)) + Lua::Callbacks::onInput(lua_state); + else + lua_pop(lua_state, 1); + + lua_getglobal(lua_state, "onFrame"); + if (lua_isfunction(lua_state, -1)) + Lua::Callbacks::onFrame(lua_state); + else + lua_pop(lua_state, 1); + + lua_getglobal(lua_state, "onPaint"); + if (lua_isfunction(lua_state, -1)) + Lua::Callbacks::onPaint(lua_state); + else + lua_pop(lua_state, 1); + +} + +const std::string& Lua::Main::currentFile() +{ + return luaFile; } void Lua::Main::reset(Context* context) diff --git a/src/program/lua/Main.h b/src/program/lua/Main.h index 9be672eb..f28fbda9 100644 --- a/src/program/lua/Main.h +++ b/src/program/lua/Main.h @@ -38,6 +38,9 @@ namespace Main { /* Run a lua script */ void run(std::string filename); + /* Return the current executed filename */ + const std::string& currentFile(); + /* Reset the lua VM */ void reset(Context* context); diff --git a/src/program/lua/NamedLuaFunction.cpp b/src/program/lua/NamedLuaFunction.cpp new file mode 100644 index 00000000..7733d35b --- /dev/null +++ b/src/program/lua/NamedLuaFunction.cpp @@ -0,0 +1,65 @@ +/* + Copyright 2015-2020 Clément Gallet + + This file is part of libTAS. + + libTAS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libTAS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libTAS. If not, see . + */ + +#include "NamedLuaFunction.h" +#include "Main.h" + +extern "C" { +// #include +// #include +#include +} +#include + +namespace Lua { + +NamedLuaFunction::NamedLuaFunction(lua_State *L, CallbackType t) : type(t), file(Main::currentFile()), lua_state(L) +{ + function_ref = luaL_ref(lua_state, LUA_REGISTRYINDEX); +} + +NamedLuaFunction::~NamedLuaFunction() +{ + if (function_ref) + luaL_unref(lua_state, LUA_REGISTRYINDEX, function_ref); + function_ref = 0; +} + +/* Taken from https://stackoverflow.com/a/21947358 */ +void NamedLuaFunction::call() +{ + /* push the callback onto the stack using the Lua reference we */ + /* stored in the registry */ + lua_rawgeti( lua_state, LUA_REGISTRYINDEX, function_ref ); + + /* duplicate the value on the stack */ + lua_pushvalue( lua_state, 1 ); + + /* call the callback */ + /* NOTE: This is using the one we duplicated with lua_pushvalue */ + if ( 0 != lua_pcall( lua_state, 0, 0, 0 ) ) { + std::cerr << "Failed to call the callback: " << lua_tostring( lua_state, -1 ) << std::endl; + return; + } + + /* get a new reference to the Lua function and store it again */ + function_ref = luaL_ref( lua_state, LUA_REGISTRYINDEX ); +} + +} diff --git a/src/program/lua/NamedLuaFunction.h b/src/program/lua/NamedLuaFunction.h new file mode 100644 index 00000000..95ecfca3 --- /dev/null +++ b/src/program/lua/NamedLuaFunction.h @@ -0,0 +1,59 @@ +/* + Copyright 2015-2020 Clément Gallet + + This file is part of libTAS. + + libTAS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libTAS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libTAS. If not, see . + */ + +#ifndef LIBTAS_NAMEDLUAFUNCTION_H_INCLUDED +#define LIBTAS_NAMEDLUAFUNCTION_H_INCLUDED + +#include + +extern "C" { +#include +} + +namespace Lua { + +class NamedLuaFunction { + +public: + + enum CallbackType { + CallbackStartup, + CallbackInput, + CallbackFrame, + CallbackPaint, + }; + + NamedLuaFunction(lua_State *L, CallbackType t); + ~NamedLuaFunction(); + + NamedLuaFunction(const NamedLuaFunction&) = delete; + + void call(); + + CallbackType type; + const std::string& file; + +private: + lua_State *lua_state; + int function_ref = 0; + +}; +} + +#endif