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