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