The Battle for Wesnoth  1.17.0-dev
application_lua_kernel.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2021
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/lauxlib.h"
54 #include "lua/lua.h"
55 #include "lua/luaconf.h"
56 
57 struct lua_State;
58 
59 static lg::log_domain log_scripting_lua("scripting/lua");
60 #define DBG_LUA LOG_STREAM(debug, log_scripting_lua)
61 #define LOG_LUA LOG_STREAM(info, log_scripting_lua)
62 #define WRN_LUA LOG_STREAM(warn, log_scripting_lua)
63 #define ERR_LUA LOG_STREAM(err, log_scripting_lua)
64 
66 {
67  std::cerr << "describe plugins (" << plugins_manager::get()->size() << "):\n";
68  lua_getglobal(L, "print");
69  for (std::size_t i = 0; i < plugins_manager::get()->size(); ++i) {
70  lua_pushvalue(L,-1); //duplicate the print
71 
72  std::stringstream line;
73  line << i
74  << ":\t"
76  << "\t\t"
78  << "\n";
79 
80  DBG_LUA << line.str();
81 
82  lua_pushstring(L, line.str().c_str());
83  lua_call(L, 1, 0);
84  }
85  if (!plugins_manager::get()->size()) {
86  lua_pushstring(L, "No plugins available.\n");
87  lua_call(L, 1, 0);
88  }
89  return 0;
90 }
91 
92 static int intf_delay(lua_State* L)
93 {
94  unsigned int delay = static_cast<unsigned int>(luaL_checkinteger(L, 1));
95  SDL_Delay(delay);
96  return 0;
97 }
98 
100  : lua_kernel_base()
101 {
102  lua_getglobal(mState, "wesnoth");
104  lua_setfield(mState, -2, "delay");
105 
106  lua_settop(mState, 0);
107 
109  lua_setglobal(mState, "describe_plugins");
110  lua_settop(mState, 0);
111 
112  // Create the preferences table.
114 }
115 
116 application_lua_kernel::thread::thread(lua_State * T) : T_(T), started_(false) {}
117 
119 {
120  if (!started_) {
121  if (lua_status(T_) == LUA_OK) {
122  return "not started";
123  } else {
124  return "load error";
125  }
126  }
127  switch (lua_status(T_)) {
128  case LUA_OK:
129  return "dead";
130  case LUA_YIELD:
131  return "started";
132  default:
133  return "error";
134  }
135 }
136 
138  return started_ ? (lua_status(T_) == LUA_YIELD) : (lua_status(T_) == LUA_OK);
139 }
140 
141 static char * v_threadtableKey = 0;
142 static void * const threadtableKey = static_cast<void *> (& v_threadtableKey);
143 
145 {
147  lua_pushvalue(L,1); // duplicate script key, since we need to store later
148  // stack is now [script key] [script key]
149 
150  lua_rawget(L, LUA_REGISTRYINDEX); // get the script table from the registry, on the top of the stack
151  if (!lua_istable(L,-1)) { // if it doesn't exist create it
152  lua_pop(L,1);
153  lua_newtable(L);
154  } // stack is now [script key] [table]
155 
156  lua_pushinteger(L, lua_rawlen(L, -1) + 1); // push #table + 1 onto the stack
157 
158  lua_State * T = lua_newthread(L); // create new thread T
159  // stack is now [script key] [table] [#table + 1] [thread]
160  lua_rawset(L, -3); // store the new thread at #table +1 index of the table.
161  // stack is now [script key] [table]
163  // stack L is now empty
164  return T; // now we can set up T's stack appropriately
165 }
166 
168 {
170  // now we are operating on T's stack, leaving a compiled C function on it.
171 
172  DBG_LUA << "created thread: status = " << lua_status(T) << (lua_status(T) == LUA_OK ? " == OK" : " == ?") << "\n";
173  DBG_LUA << "loading script from string:\n<<\n" << prog << "\n>>\n";
174 
175  // note: this is unsafe for umc as it allows loading lua baytecode, but umc cannot add application lua kernel scipts.
176  int errcode = luaL_loadstring(T, prog.c_str());
177  if (errcode != LUA_OK) {
178  const char * err_str = lua_tostring(T, -1);
179  std::string msg = err_str ? err_str : "null string";
180 
181  std::string context = "When parsing a string to a lua thread, ";
182 
183  if (errcode == LUA_ERRSYNTAX) {
184  context += " a syntax error";
185  } else if(errcode == LUA_ERRMEM){
186  context += " a memory error";
187  } else {
188  context += " an unknown error";
189  }
190 
191  throw game::lua_error(msg, context);
192  }
193  if (!lua_kernel_base::protected_call(T, 0, 1, std::bind(&lua_kernel_base::log_error, this, std::placeholders::_1, std::placeholders::_2))) {
194  throw game::lua_error("Error when executing a script to make a lua thread.");
195  }
196  if (!lua_isfunction(T, -1)) {
197  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)) );
198  }
199 
200  return new application_lua_kernel::thread(T);
201 }
202 
204 {
206  // now we are operating on T's stack, leaving a compiled C function on it.
207 
208  lua_pushstring(T, file.c_str());
210  if (!lua_kernel_base::protected_call(T, 0, 1, std::bind(&lua_kernel_base::log_error, this, std::placeholders::_1, std::placeholders::_2))) {
211  throw game::lua_error("Error when executing a file to make a lua thread.");
212  }
213  if (!lua_isfunction(T, -1)) {
214  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)) );
215  }
216 
217  return new application_lua_kernel::thread(T);
218 }
219 
221  std::vector<plugins_manager::event> requests;
222  bool valid;
223 
225  : requests()
226  , valid(true)
227  {}
228 };
229 
230 static int impl_context_backend(lua_State * L, std::shared_ptr<lua_context_backend> backend, std::string req_name)
231 {
232  if (!backend->valid) {
233  luaL_error(L , "Error, you tried to use an invalid context object in a lua thread");
234  }
235 
237  evt.name = req_name;
238  evt.data = luaW_checkconfig(L, -1);
239 
240  backend->requests.push_back(evt);
241  return 0;
242 }
243 
244 static int impl_context_accessor(lua_State * L, std::shared_ptr<lua_context_backend> backend, plugins_context::accessor_function func)
245 {
246  if (!backend->valid) {
247  luaL_error(L , "Error, you tried to use an invalid context object in a lua thread");
248  }
249 
250  if(lua_gettop(L)) {
251  config temp;
252  if(!luaW_toconfig(L, 1, temp)) {
253  luaL_argerror(L, 1, "Error, tried to parse a config but some fields were invalid");
254  }
255  luaW_pushconfig(L, func(temp));
256  return 1;
257  } else {
258  luaW_pushconfig(L, func(config()));
259  return 1;
260  }
261 }
262 
263 application_lua_kernel::request_list application_lua_kernel::thread::run_script(const plugins_context & ctxt, const std::vector<plugins_manager::event> & queue)
264 {
265  // There are two possibilities: (1) this is the first execution, and the C function is the only thing on the stack
266  // (2) this is a subsequent execution, and there is nothing on the stack.
267  // Either way we push the arguments to the function and call resume.
268 
269  // First we have to create the event table, by concatenating the event queue into a table.
270  lua_newtable(T_); //this will be the event table
271  for (std::size_t i = 0; i < queue.size(); ++i) {
272  lua_newtable(T_);
273  lua_pushstring(T_, queue[i].name.c_str());
274  lua_rawseti(T_, -2, 1);
275  luaW_pushconfig(T_, queue[i].data);
276  lua_rawseti(T_, -2, 2);
277  lua_rawseti(T_, -2, i+1);
278  }
279 
280  // Now we have to create the context object. It is arranged as a table of boost functions.
281  auto this_context_backend = std::make_shared<lua_context_backend>();
282  lua_newtable(T_); // this will be the context table
283  for (const std::string & key : ctxt.callbacks_ | boost::adaptors::map_keys ) {
284  lua_pushstring(T_, key.c_str());
285  lua_cpp::push_function(T_, std::bind(&impl_context_backend, std::placeholders::_1, this_context_backend, key));
286  lua_settable(T_, -3);
287  }
288 
289  // Now we have to create the info object (context accessors). It is arranged as a table of boost functions.
290  lua_newtable(T_); // this will be the info table
291  lua_pushstring(T_, "name");
292  lua_pushstring(T_, ctxt.name_.c_str());
293  lua_settable(T_, -3);
294  for (const plugins_context::accessor_list::value_type & v : ctxt.accessors_) {
295  const std::string & key = v.first;
296  const plugins_context::accessor_function & func = v.second;
297  lua_pushstring(T_, key.c_str());
298  lua_cpp::push_function(T_, std::bind(&impl_context_accessor, std::placeholders::_1, this_context_backend, func));
299  lua_settable(T_, -3);
300  }
301 
302  // Now we resume the function, calling the coroutine with the three arguments (events, context, info).
303  int numres = 0;
304  lua_resume(T_, nullptr, 3, &numres);
305 
306  started_ = true;
307 
308  this_context_backend->valid = false; //invalidate the context object for lua
309 
310  if (lua_status(T_) != LUA_YIELD) {
311  LOG_LUA << "Thread status = '" << lua_status(T_) << "'\n";
312  if (lua_status(T_) != LUA_OK) {
313  std::stringstream ss;
314  ss << "encountered a";
315  switch(lua_status(T_)) {
316  case LUA_ERRSYNTAX:
317  ss << " syntax ";
318  break;
319  case LUA_ERRRUN:
320  ss << " runtime ";
321  break;
322  case LUA_ERRERR:
323  ss << " error-handler ";
324  break;
325  default:
326  ss << " ";
327  break;
328  }
329  ss << "error:\n" << lua_tostring(T_, -1) << "\n";
330  ERR_LUA << ss.str() << std::endl;
331  }
332  }
333 
335 
336  for (const plugins_manager::event & req : this_context_backend->requests) {
337  results.push_back(std::bind(ctxt.callbacks_.find(req.name)->second, req.data));
338  //results.emplace_back(ctxt.callbacks_.find(req.name)->second, req.data);
339  }
340  return results;
341 }
LUA_API void lua_pushlightuserdata(lua_State *L, void *p)
Definition: lapi.cpp:592
#define lua_pushcfunction(L, f)
Definition: lua.h:370
thread(const thread &)=delete
callback_list callbacks_
Definition: context.hpp:67
static void *const threadtableKey
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:812
LUA_API void lua_settop(lua_State *L, int idx)
Definition: lapi.cpp:173
LUA_API int lua_type(lua_State *L, int idx)
Definition: lapi.cpp:260
LUALIB_API int luaL_loadstring(lua_State *L, const char *s)
Definition: lauxlib.cpp:815
LUA_API void lua_rawseti(lua_State *L, int idx, lua_Integer n)
Definition: lapi.cpp:889
LUA_API int lua_gettop(lua_State *L)
Definition: lapi.cpp:168
LUA_API int lua_getglobal(lua_State *L, const char *name)
Definition: lapi.cpp:632
LUA_API void lua_settable(lua_State *L, int idx)
Definition: lapi.cpp:821
static int impl_context_backend(lua_State *L, std::shared_ptr< lua_context_backend > backend, std::string req_name)
LUA_API int lua_rawget(lua_State *L, int idx)
Definition: lapi.cpp:698
static int impl_context_accessor(lua_State *L, std::shared_ptr< lua_context_backend > backend, plugins_context::accessor_function func)
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:110
#define LOG_LUA
thread * load_script_from_string(const std::string &)
Definitions for the interface to Wesnoth Markup Language (WML).
void push_function(lua_State *L, const lua_function &f)
Pushes a std::function wrapper object onto the stack.
#define lua_pop(L, n)
Definition: lua.h:364
static lg::log_domain log_scripting_lua("scripting/lua")
LUALIB_API int luaL_argerror(lua_State *L, int arg, const char *extramsg)
Definition: lauxlib.cpp:175
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
static int intf_delay(lua_State *L)
std::string get_name(std::size_t idx)
Definition: manager.cpp:95
std::vector< std::function< bool(void)> > request_list
#define ERR_LUA
#define LUA_YIELD
Definition: lua.h:50
static char * v_threadtableKey
std::vector< plugins_manager::event > requests
static lua_State * get_new_thread(lua_State *L)
bool protected_call(int nArgs, int nRets, error_handler)
LUA_API void lua_setglobal(lua_State *L, const char *name)
Definition: lapi.cpp:813
LUA_API int lua_status(lua_State *L)
Definition: lapi.cpp:1093
#define lua_newtable(L)
Definition: lua.h:366
LUA_API int lua_resume(lua_State *L, lua_State *from, int nargs, int *nresults)
Definition: ldo.cpp:701
#define DBG_LUA
bool luaW_toconfig(lua_State *L, int index, config &cfg)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:824
#define LUA_ERRERR
Definition: lua.h:54
std::size_t i
Definition: function.cpp:967
LUALIB_API lua_Integer luaL_checkinteger(lua_State *L, int arg)
Definition: lauxlib.cpp:442
LUA_API void lua_rawset(lua_State *L, int idx)
Definition: lapi.cpp:877
config luaW_checkconfig(lua_State *L, int index)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:901
std::string name
Definition: manager.hpp:65
accessor_list accessors_
Definition: context.hpp:68
#define lua_isfunction(L, n)
Definition: lua.h:372
#define LUA_ERRSYNTAX
Definition: lua.h:52
#define lua_tostring(L, i)
Definition: lua.h:386
LUA_API void lua_pushvalue(lua_State *L, int idx)
Definition: lapi.cpp:246
#define LUA_ERRMEM
Definition: lua.h:53
int load_file(lua_State *L)
Loads a Lua file and pushes the contents on the stack.
std::function< config(config)> accessor_function
Definition: context.hpp:37
virtual void log_error(char const *msg, char const *context="Lua error")
Error reporting mechanisms, used by virtual methods protected_call and load_string.
#define lua_call(L, n, r)
Definition: lua.h:283
LUALIB_API int luaL_error(lua_State *L, const char *fmt,...)
Definition: lauxlib.cpp:234
std::string register_table(lua_State *L)
command_log cmd_log_
LUA_API lua_Unsigned lua_rawlen(lua_State *L, int idx)
Definition: lapi.cpp:402
#define LUA_REGISTRYINDEX
Definition: lua.h:44
LUA_API int lua_error(lua_State *L)
Definition: lapi.cpp:1205
#define LUA_OK
Definition: lua.h:49
#define lua_istable(L, n)
Definition: lua.h:373
request_list run_script(const plugins_context &ctxt, const std::vector< plugins_manager::event > &queue)
Standard logging facilities (interface).
STATUS get_status(std::size_t idx)
Definition: manager.cpp:73
std::size_t size()
Definition: manager.cpp:69
LUA_API lua_State * lua_newthread(lua_State *L)
Definition: lstate.cpp:285
static int intf_describe_plugins(lua_State *L)
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
std::string name_
Definition: context.hpp:69
lua_State * mState
LUA_API void lua_pushinteger(lua_State *L, lua_Integer n)
Definition: lapi.cpp:489
LUA_API const char * lua_pushstring(lua_State *L, const char *s)
Definition: lapi.cpp:514
LUA_API void lua_setfield(lua_State *L, int idx, const char *k)
Definition: lapi.cpp:837
#define LUA_ERRRUN
Definition: lua.h:51
thread * load_script_from_file(const std::string &)
LUA_API const char * lua_typename(lua_State *L, int t)
Definition: lapi.cpp:266
static plugins_manager * get()
Definition: manager.cpp:59