The Battle for Wesnoth  1.19.0-dev
application_lua_kernel.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2024
3  by Chris Beck <render787@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Provides a Lua interpreter, to drive the game_controller.
19  *
20  * @note Naming conventions:
21  * - intf_ functions are exported in the wesnoth domain,
22  * - impl_ functions are hidden inside metatables,
23  * - cfun_ functions are closures,
24  * - luaW_ functions are helpers in Lua style.
25  */
26 
28 
29 #include "config.hpp"
30 #include "game_errors.hpp"
31 #include "log.hpp"
32 #include "scripting/lua_common.hpp"
39 
40 #ifdef DEBUG_LUA
41 #include "scripting/debug_lua.hpp"
42 #endif
43 
44 #include <map>
45 #include <sstream>
46 #include <string>
47 #include <utility>
48 
49 #include <functional>
50 #include <boost/range/adaptors.hpp>
51 #include <SDL2/SDL.h>
52 
53 #include "lua/wrapper_lauxlib.h"
54 
55 
56 static lg::log_domain log_scripting_lua("scripting/lua");
57 #define DBG_LUA LOG_STREAM(debug, log_scripting_lua)
58 #define LOG_LUA LOG_STREAM(info, log_scripting_lua)
59 #define WRN_LUA LOG_STREAM(warn, log_scripting_lua)
60 #define ERR_LUA LOG_STREAM(err, log_scripting_lua)
61 
62 static int intf_describe_plugins(lua_State * L)
63 {
64  PLAIN_LOG << "describe plugins (" << plugins_manager::get()->size() << "):";
65  lua_getglobal(L, "print");
66  for (std::size_t i = 0; i < plugins_manager::get()->size(); ++i) {
67  lua_pushvalue(L,-1); //duplicate the print
68 
69  std::stringstream line;
70  line << i
71  << ":\t"
73  << "\t\t"
75  << "\n";
76 
77  DBG_LUA << line.str();
78 
79  lua_pushstring(L, line.str().c_str());
80  lua_call(L, 1, 0);
81  }
82  if (!plugins_manager::get()->size()) {
83  lua_pushstring(L, "No plugins available.\n");
84  lua_call(L, 1, 0);
85  }
86  return 0;
87 }
88 
89 static int intf_delay(lua_State* L)
90 {
91  unsigned int delay = static_cast<unsigned int>(luaL_checkinteger(L, 1));
92  SDL_Delay(delay);
93  return 0;
94 }
95 
97  : lua_kernel_base()
98 {
99  lua_getglobal(mState, "wesnoth");
100  lua_pushcfunction(mState, intf_delay);
101  lua_setfield(mState, -2, "delay");
102 
103  lua_settop(mState, 0);
104 
105  lua_pushcfunction(mState, &intf_describe_plugins);
106  lua_setglobal(mState, "describe_plugins");
107  lua_settop(mState, 0);
108 
109  // Create the preferences table.
111 }
112 
113 application_lua_kernel::thread::thread(lua_State * T) : T_(T), started_(false) {}
114 
116 {
117  if (!started_) {
118  if (lua_status(T_) == LUA_OK) {
119  return "not started";
120  } else {
121  return "load error";
122  }
123  }
124  switch (lua_status(T_)) {
125  case LUA_OK:
126  return "dead";
127  case LUA_YIELD:
128  return "started";
129  default:
130  return "error";
131  }
132 }
133 
135  return started_ ? (lua_status(T_) == LUA_YIELD) : (lua_status(T_) == LUA_OK);
136 }
137 
138 static char * v_threadtableKey = 0;
139 static void * const threadtableKey = static_cast<void *> (& v_threadtableKey);
140 
141 static lua_State * get_new_thread(lua_State * L)
142 {
143  lua_pushlightuserdata(L , threadtableKey);
144  lua_pushvalue(L,1); // duplicate script key, since we need to store later
145  // stack is now [script key] [script key]
146 
147  lua_rawget(L, LUA_REGISTRYINDEX); // get the script table from the registry, on the top of the stack
148  if (!lua_istable(L,-1)) { // if it doesn't exist create it
149  lua_pop(L,1);
150  lua_newtable(L);
151  } // stack is now [script key] [table]
152 
153  lua_pushinteger(L, lua_rawlen(L, -1) + 1); // push #table + 1 onto the stack
154 
155  lua_State * T = lua_newthread(L); // create new thread T
156  // stack is now [script key] [table] [#table + 1] [thread]
157  lua_rawset(L, -3); // store the new thread at #table +1 index of the table.
158  // stack is now [script key] [table]
159  lua_rawset(L, LUA_REGISTRYINDEX);
160  // stack L is now empty
161  return T; // now we can set up T's stack appropriately
162 }
163 
165 {
166  lua_State * T = get_new_thread(mState);
167  // now we are operating on T's stack, leaving a compiled C function on it.
168 
169  DBG_LUA << "created thread: status = " << lua_status(T) << (lua_status(T) == LUA_OK ? " == OK" : " == ?");
170  DBG_LUA << "loading script from string:\n<<\n" << prog << "\n>>";
171 
172  // note: this is unsafe for umc as it allows loading lua baytecode, but umc cannot add application lua kernel scipts.
173  int errcode = luaL_loadstring(T, prog.c_str());
174  if (errcode != LUA_OK) {
175  const char * err_str = lua_tostring(T, -1);
176  std::string msg = err_str ? err_str : "null string";
177 
178  std::string context = "When parsing a string to a lua thread, ";
179 
180  if (errcode == LUA_ERRSYNTAX) {
181  context += " a syntax error";
182  } else if(errcode == LUA_ERRMEM){
183  context += " a memory error";
184  } else {
185  context += " an unknown error";
186  }
187 
188  throw game::lua_error(msg, context);
189  }
190  if (!lua_kernel_base::protected_call(T, 0, 1, std::bind(&lua_kernel_base::log_error, this, std::placeholders::_1, std::placeholders::_2))) {
191  throw game::lua_error("Error when executing a script to make a lua thread.");
192  }
193  if (!lua_isfunction(T, -1)) {
194  throw game::lua_error(std::string("Error when executing a script to make a lua thread -- function was not produced, found a ") + lua_typename(T, lua_type(T, -1)) );
195  }
196 
197  return new application_lua_kernel::thread(T);
198 }
199 
201 {
202  lua_State * T = get_new_thread(mState);
203  // now we are operating on T's stack, leaving a compiled C function on it.
204 
205  lua_pushstring(T, file.c_str());
207  if (!lua_kernel_base::protected_call(T, 0, 1, std::bind(&lua_kernel_base::log_error, this, std::placeholders::_1, std::placeholders::_2))) {
208  throw game::lua_error("Error when executing a file to make a lua thread.");
209  }
210  if (!lua_isfunction(T, -1)) {
211  throw game::lua_error(std::string("Error when executing a file to make a lua thread -- function was not produced, found a ") + lua_typename(T, lua_type(T, -1)) );
212  }
213 
214  return new application_lua_kernel::thread(T);
215 }
216 
218  std::vector<plugins_manager::event> requests;
219  bool valid;
220 
222  : requests()
223  , valid(true)
224  {}
225 };
226 
227 static int impl_context_backend(lua_State * L, std::shared_ptr<lua_context_backend> backend, std::string req_name)
228 {
229  if (!backend->valid) {
230  luaL_error(L , "Error, you tried to use an invalid context object in a lua thread");
231  }
232 
234  evt.name = req_name;
235  evt.data = luaW_checkconfig(L, -1);
236 
237  backend->requests.push_back(evt);
238  return 0;
239 }
240 
241 static int impl_context_accessor(lua_State * L, std::shared_ptr<lua_context_backend> backend, plugins_context::accessor_function func)
242 {
243  if (!backend->valid) {
244  luaL_error(L , "Error, you tried to use an invalid context object in a lua thread");
245  }
246 
247  if(lua_gettop(L)) {
248  config temp;
249  if(!luaW_toconfig(L, 1, temp)) {
250  luaL_argerror(L, 1, "Error, tried to parse a config but some fields were invalid");
251  }
252  luaW_pushconfig(L, func(temp));
253  return 1;
254  } else {
255  luaW_pushconfig(L, func(config()));
256  return 1;
257  }
258 }
259 
260 application_lua_kernel::request_list application_lua_kernel::thread::run_script(const plugins_context & ctxt, const std::vector<plugins_manager::event> & queue)
261 {
262  // There are two possibilities: (1) this is the first execution, and the C function is the only thing on the stack
263  // (2) this is a subsequent execution, and there is nothing on the stack.
264  // Either way we push the arguments to the function and call resume.
265 
266  // First we have to create the event table, by concatenating the event queue into a table.
267  lua_newtable(T_); //this will be the event table
268  for (std::size_t i = 0; i < queue.size(); ++i) {
269  lua_newtable(T_);
270  lua_pushstring(T_, queue[i].name.c_str());
271  lua_rawseti(T_, -2, 1);
272  luaW_pushconfig(T_, queue[i].data);
273  lua_rawseti(T_, -2, 2);
274  lua_rawseti(T_, -2, i+1);
275  }
276 
277  // Now we have to create the context object. It is arranged as a table of boost functions.
278  auto this_context_backend = std::make_shared<lua_context_backend>();
279  lua_newtable(T_); // this will be the context table
280  for (const std::string & key : ctxt.callbacks_ | boost::adaptors::map_keys ) {
281  lua_pushstring(T_, key.c_str());
282  lua_cpp::push_function(T_, std::bind(&impl_context_backend, std::placeholders::_1, this_context_backend, key));
283  lua_settable(T_, -3);
284  }
285 
286  // Now we have to create the info object (context accessors). It is arranged as a table of boost functions.
287  lua_newtable(T_); // this will be the info table
288  lua_pushstring(T_, "name");
289  lua_pushstring(T_, ctxt.name_.c_str());
290  lua_settable(T_, -3);
291  for (const plugins_context::accessor_list::value_type & v : ctxt.accessors_) {
292  const std::string & key = v.first;
293  const plugins_context::accessor_function & func = v.second;
294  lua_pushstring(T_, key.c_str());
295  lua_cpp::push_function(T_, std::bind(&impl_context_accessor, std::placeholders::_1, this_context_backend, func));
296  lua_settable(T_, -3);
297  }
298 
299  // Now we resume the function, calling the coroutine with the three arguments (events, context, info).
300  int numres = 0;
301  lua_resume(T_, nullptr, 3, &numres);
302 
303  started_ = true;
304 
305  this_context_backend->valid = false; //invalidate the context object for lua
306 
307  if (lua_status(T_) != LUA_YIELD) {
308  LOG_LUA << "Thread status = '" << lua_status(T_) << "'";
309  if (lua_status(T_) != LUA_OK) {
310  std::stringstream ss;
311  ss << "encountered a";
312  switch(lua_status(T_)) {
313  case LUA_ERRSYNTAX:
314  ss << " syntax ";
315  break;
316  case LUA_ERRRUN:
317  ss << " runtime ";
318  break;
319  case LUA_ERRERR:
320  ss << " error-handler ";
321  break;
322  default:
323  ss << " ";
324  break;
325  }
326  ss << "error:\n" << lua_tostring(T_, -1) << "\n";
327  ERR_LUA << ss.str();
328  }
329  }
330 
332 
333  for (const plugins_manager::event & req : this_context_backend->requests) {
334  results.push_back(std::bind(ctxt.callbacks_.find(req.name)->second, req.data));
335  //results.emplace_back(ctxt.callbacks_.find(req.name)->second, req.data);
336  }
337  return results;
338 }
static lua_State * get_new_thread(lua_State *L)
#define ERR_LUA
static lg::log_domain log_scripting_lua("scripting/lua")
static int intf_describe_plugins(lua_State *L)
static int impl_context_accessor(lua_State *L, std::shared_ptr< lua_context_backend > backend, plugins_context::accessor_function func)
static char * v_threadtableKey
#define LOG_LUA
static void *const threadtableKey
static int intf_delay(lua_State *L)
#define DBG_LUA
static int impl_context_backend(lua_State *L, std::shared_ptr< lua_context_backend > backend, std::string req_name)
request_list run_script(const plugins_context &ctxt, const std::vector< plugins_manager::event > &queue)
thread(const thread &)=delete
thread * load_script_from_string(const std::string &)
std::vector< std::function< bool(void)> > request_list
thread * load_script_from_file(const std::string &)
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
virtual void log_error(char const *msg, char const *context="Lua error")
Error reporting mechanisms, used by virtual methods protected_call and load_string.
command_log cmd_log_
lua_State * mState
bool protected_call(int nArgs, int nRets, error_handler)
std::string name_
Definition: context.hpp:69
callback_list callbacks_
Definition: context.hpp:67
accessor_list accessors_
Definition: context.hpp:68
std::function< config(config)> accessor_function
Definition: context.hpp:37
std::size_t size()
Definition: manager.cpp:68
std::string get_name(std::size_t idx)
Definition: manager.cpp:94
static plugins_manager * get()
Definition: manager.cpp:58
std::size_t i
Definition: function.cpp:968
Standard logging facilities (interface).
#define PLAIN_LOG
Definition: log.hpp:295
void luaW_pushconfig(lua_State *L, const config &cfg)
Converts a config object to a Lua table pushed at the top of the stack.
Definition: lua_common.cpp:827
config luaW_checkconfig(lua_State *L, int index)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:917
bool luaW_toconfig(lua_State *L, int index, config &cfg)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:839
void line(int from_x, int from_y, int to_x, int to_y)
Draw a line.
Definition: draw.cpp:180
void push_function(lua_State *L, const lua_function &f)
Pushes a std::function wrapper object onto the stack.
int load_file(lua_State *L)
Loads a Lua file and pushes the contents on the stack.
std::string register_table(lua_State *L)
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
std::string_view data
Definition: picture.cpp:194
Error used to report an error in a lua script or in the lua interpreter.
Definition: game_errors.hpp:54
std::vector< plugins_manager::event > requests
std::string name
Definition: manager.hpp:59
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46