The Battle for Wesnoth  1.19.8+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_config.hpp"
31 #include "game_errors.hpp"
32 #include "log.hpp"
34 #include "scripting/lua_common.hpp"
41 #include "scripting/push_check.hpp"
42 #include "utils/ranges.hpp"
43 
44 #ifdef DEBUG_LUA
45 #include "scripting/debug_lua.hpp"
46 #endif
47 
48 #include <map>
49 #include <sstream>
50 #include <string>
51 #include <thread>
52 #include <utility>
53 
54 #include <functional>
55 
56 #include "lua/wrapper_lauxlib.h"
57 
58 static lg::log_domain log_scripting_lua("scripting/lua");
59 #define DBG_LUA LOG_STREAM(debug, log_scripting_lua)
60 #define LOG_LUA LOG_STREAM(info, log_scripting_lua)
61 #define WRN_LUA LOG_STREAM(warn, log_scripting_lua)
62 #define ERR_LUA LOG_STREAM(err, log_scripting_lua)
63 
64 static int intf_describe_plugins(lua_State * L)
65 {
66  PLAIN_LOG << "describe plugins (" << plugins_manager::get()->size() << "):";
67  lua_getglobal(L, "print");
68  for (std::size_t i = 0; i < plugins_manager::get()->size(); ++i) {
69  lua_pushvalue(L,-1); //duplicate the print
70 
71  std::stringstream line;
72  line << i
73  << ":\t"
75  << "\t\t"
77  << "\n";
78 
79  DBG_LUA << line.str();
80 
81  lua_pushstring(L, line.str().c_str());
82  lua_call(L, 1, 0);
83  }
84  if (!plugins_manager::get()->size()) {
85  lua_pushstring(L, "No plugins available.\n");
86  lua_call(L, 1, 0);
87  }
88  return 0;
89 }
90 
91 static int intf_delay(lua_State* L)
92 {
93  std::this_thread::sleep_for(std::chrono::milliseconds{luaL_checkinteger(L, 1)});
94  return 0;
95 }
96 
97 static int intf_execute(lua_State* L);
98 
100  : lua_kernel_base()
101 {
102  lua_getglobal(mState, "wesnoth");
103  lua_pushcfunction(mState, intf_delay);
104  lua_setfield(mState, -2, "delay");
105 
106  lua_settop(mState, 0);
107 
108  lua_pushcfunction(mState, &intf_describe_plugins);
109  lua_setglobal(mState, "describe_plugins");
110  lua_settop(mState, 0);
111 
112  // Create the preferences table.
114 
115  // Create the wesnoth.plugin table
116  luaW_getglobal(mState, "wesnoth");
117  lua_newtable(mState);
118  lua_pushcfunction(mState, intf_execute);
119  lua_setfield(mState, -2, "execute");
120  lua_setfield(mState, -2, "plugin");
121  lua_pop(mState, 1);
122 }
123 
124 application_lua_kernel::thread::thread(application_lua_kernel& owner, lua_State * T) : owner_(owner), T_(T), started_(false) {}
125 
127 {
128  if (!started_) {
129  if (lua_status(T_) == LUA_OK) {
130  return "not started";
131  } else {
132  return "load error";
133  }
134  }
135  switch (lua_status(T_)) {
136  case LUA_OK:
137  return "dead";
138  case LUA_YIELD:
139  return "started";
140  default:
141  return "error";
142  }
143 }
144 
146  return started_ ? (lua_status(T_) == LUA_YIELD) : (lua_status(T_) == LUA_OK);
147 }
148 
149 static char * v_threadtableKey = nullptr;
150 static void * const threadtableKey = static_cast<void *> (& v_threadtableKey);
151 
152 static lua_State * get_new_thread(lua_State * L)
153 {
154  lua_pushlightuserdata(L , threadtableKey);
155  lua_pushvalue(L,1); // duplicate script key, since we need to store later
156  // stack is now [script key] [script key]
157 
158  lua_rawget(L, LUA_REGISTRYINDEX); // get the script table from the registry, on the top of the stack
159  if (!lua_istable(L,-1)) { // if it doesn't exist create it
160  lua_pop(L,1);
161  lua_newtable(L);
162  } // stack is now [script key] [table]
163 
164  lua_pushinteger(L, lua_rawlen(L, -1) + 1); // push #table + 1 onto the stack
165 
166  lua_State * T = lua_newthread(L); // create new thread T
167  // stack is now [script key] [table] [#table + 1] [thread]
168  lua_rawset(L, -3); // store the new thread at #table +1 index of the table.
169  // stack is now [script key] [table]
170  lua_rawset(L, LUA_REGISTRYINDEX);
171  // stack L is now empty
172  return T; // now we can set up T's stack appropriately
173 }
174 
176 {
177  lua_State * T = get_new_thread(mState);
178  // now we are operating on T's stack, leaving a compiled C function on it.
179 
180  DBG_LUA << "created thread: status = " << lua_status(T) << (lua_status(T) == LUA_OK ? " == OK" : " == ?");
181  DBG_LUA << "loading script from string:\n<<\n" << prog << "\n>>";
182 
183  // note: this is unsafe for umc as it allows loading lua baytecode, but umc cannot add application lua kernel scipts.
184  int errcode = luaL_loadstring(T, prog.c_str());
185  if (errcode != LUA_OK) {
186  const char * err_str = lua_tostring(T, -1);
187  std::string msg = err_str ? err_str : "null string";
188 
189  std::string context = "When parsing a string to a lua thread, ";
190 
191  if (errcode == LUA_ERRSYNTAX) {
192  context += " a syntax error";
193  } else if(errcode == LUA_ERRMEM){
194  context += " a memory error";
195  } else {
196  context += " an unknown error";
197  }
198 
199  throw game::lua_error(msg, context);
200  }
201  if (!lua_kernel_base::protected_call(T, 0, 1, std::bind(&lua_kernel_base::log_error, this, std::placeholders::_1, std::placeholders::_2))) {
202  throw game::lua_error("Error when executing a script to make a lua thread.");
203  }
204  if (!lua_isfunction(T, -1)) {
205  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)) );
206  }
207 
208  return new application_lua_kernel::thread(*this, T);
209 }
210 
212 {
213  lua_State * T = get_new_thread(mState);
214  // now we are operating on T's stack, leaving a compiled C function on it.
215 
216  lua_pushstring(T, file.c_str());
218  if (!lua_kernel_base::protected_call(T, 0, 1, std::bind(&lua_kernel_base::log_error, this, std::placeholders::_1, std::placeholders::_2))) {
219  throw game::lua_error("Error when executing a file to make a lua thread.");
220  }
221  if (!lua_isfunction(T, -1)) {
222  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)) );
223  }
224 
225  return new application_lua_kernel::thread(*this, T);
226 }
227 
229  std::vector<plugins_manager::event> requests;
231  bool valid;
232 
234  : requests()
235  , valid(true)
236  {}
237 };
238 
239 static int impl_context_backend(lua_State * L, const std::shared_ptr<lua_context_backend>& backend, std::string req_name)
240 {
241  if (!backend->valid) {
242  luaL_error(L , "Error, you tried to use an invalid context object in a lua thread");
243  }
244 
246  evt.name = std::move(req_name);
247  evt.data = luaW_checkconfig(L, -1);
248 
249  backend->requests.push_back(evt);
250  return 0;
251 }
252 
253 static int impl_context_accessor(lua_State * L, const std::shared_ptr<lua_context_backend>& backend, const plugins_context::accessor_function& func)
254 {
255  if (!backend->valid) {
256  luaL_error(L , "Error, you tried to use an invalid context object in a lua thread");
257  }
258 
259  if(lua_gettop(L)) {
260  config temp;
261  if(!luaW_toconfig(L, 1, temp)) {
262  luaL_argerror(L, 1, "Error, tried to parse a config but some fields were invalid");
263  }
264  luaW_pushconfig(L, func(temp));
265  return 1;
266  } else {
267  luaW_pushconfig(L, func(config()));
268  return 1;
269  }
270 }
271 
272 extern luaW_Registry& gameConfigReg();
273 static auto& dummy = gameConfigReg(); // just to ensure it's constructed.
274 
276  (void)k;
277  game_config::set_debug(value);
278 }
279 
280 static int intf_execute(lua_State* L)
281 {
282  static const int CTX = 1, FUNC = 2, EVT = 3, EXEC = 4;
283  if(lua_gettop(L) == 2) lua_pushnil(L);
284  if(!luaW_table_get_def(L, CTX, "valid", false)) {
285  lua_pushboolean(L, false);
286  lua_pushstring(L, "context not valid");
287  return 2;
288  }
289  if(!luaW_tableget(L, CTX, "execute")) {
290  lua_pushboolean(L, false);
291  lua_pushstring(L, "context cannot execute");
292  return 2;
293  }
294  if(!lua_islightuserdata(L, EXEC)) {
295  lua_pushboolean(L, false);
296  lua_pushstring(L, "execute is not a thread");
297  return 2;
298  }
299  try {
301  if(data["params"] != 0) {
302  lua_pushboolean(L, false);
303  lua_pushstring(L, "cannot execute function with parameters");
304  return 2;
305  }
306  if(!lua_isnil(L, EVT)) data["name"] = luaL_checkstring(L, EVT);
307  lua_pushvalue(L, FUNC);
308  data["ref"] = luaL_ref(L, LUA_REGISTRYINDEX);
309  std::shared_ptr<lua_context_backend>* context = static_cast<std::shared_ptr<lua_context_backend>*>(lua_touserdata(L, EXEC));
310  luaW_pushconfig(L, data);
311  impl_context_backend(L, *context, "execute");
312  } catch(luafunc_serialize_error& e) {
313  lua_pushboolean(L, false);
314  lua_pushstring(L, e.what());
315  return 2;
316  }
317  lua_pushboolean(L, true);
318  return 1;
319 }
320 bool luaW_copy_upvalues(lua_State* L, const config& cfg);
321 application_lua_kernel::request_list application_lua_kernel::thread::run_script(const plugins_context & ctxt, const std::vector<plugins_manager::event> & queue)
322 {
323  // There are two possibilities: (1) this is the first execution, and the C function is the only thing on the stack
324  // (2) this is a subsequent execution, and there is nothing on the stack.
325  // Either way we push the arguments to the function and call resume.
326 
327  // First we have to create the event table, by concatenating the event queue into a table.
328  config events;
329  for(const auto& event : queue) {
330  events.add_child(event.name, event.data);
331  }
332  luaW_pushconfig(T_, events); //this will be the event table
333 
334  // Now we have to create the context object. It is arranged as a table of boost functions.
335  auto this_context_backend = std::make_shared<lua_context_backend>();
336  lua_newtable(T_); // this will be the context table
337  lua_pushstring(T_, "valid");
338  lua_pushboolean(T_, true);
339  lua_settable(T_, -3);
340  for (const std::string & key : ctxt.callbacks_ | utils::views::keys ) {
341  lua_pushstring(T_, key.c_str());
342  lua_cpp::push_function(T_, std::bind(&impl_context_backend, std::placeholders::_1, this_context_backend, key));
343  lua_settable(T_, -3);
344  }
345  if(ctxt.execute_kernel_) {
346  lua_pushstring(T_, "execute");
347  lua_pushlightuserdata(T_, &this_context_backend);
348  lua_settable(T_, -3);
349  }
350 
351  // Now we have to create the info object (context accessors). It is arranged as a table of boost functions.
352  lua_newtable(T_); // this will be the info table
353  lua_pushstring(T_, "name");
354  lua_pushstring(T_, ctxt.name_.c_str());
355  lua_settable(T_, -3);
356  lua_pushstring(T_, "valid");
357  lua_pushboolean(T_, true);
358  lua_settable(T_, -3);
359  for (const plugins_context::accessor_list::value_type & v : ctxt.accessors_) {
360  const std::string & key = v.first;
361  const plugins_context::accessor_function & func = v.second;
362  lua_pushstring(T_, key.c_str());
363  lua_cpp::push_function(T_, std::bind(&impl_context_accessor, std::placeholders::_1, this_context_backend, func));
364  lua_settable(T_, -3);
365  }
366 
367  // Push copies of the context and info tables so that we can mark them invalid for the next slice
368  lua_pushvalue(T_, -2);
369  lua_pushvalue(T_, -2);
370  // However, Lua can't handle having extra values on the stack when resuming a coroutine,
371  // so move the extra copy to the main thread instead.
372  lua_xmove(T_, owner_.get_state(), 2);
373  // Now we resume the function, calling the coroutine with the three arguments (events, context, info).
374  // We ignore any values returned via arguments to yield()
375  int numres = 0;
376  lua_resume(T_, nullptr, 3, &numres);
377 
378  started_ = true;
379 
380  this_context_backend->valid = false; //invalidate the context object for lua
381 
382  if (lua_status(T_) != LUA_YIELD) {
383  LOG_LUA << "Thread status = '" << lua_status(T_) << "'";
384  if (lua_status(T_) != LUA_OK) {
385  std::stringstream ss;
386  ss << "encountered a";
387  switch(lua_status(T_)) {
388  case LUA_ERRSYNTAX:
389  ss << " syntax ";
390  break;
391  case LUA_ERRRUN:
392  ss << " runtime ";
393  break;
394  case LUA_ERRERR:
395  ss << " error-handler ";
396  break;
397  default:
398  ss << " ";
399  break;
400  }
401  ss << "error:\n" << lua_tostring(T_, -1) << "\n";
402  ERR_LUA << ss.str();
403  }
404  }
405 
406  // Pop any values that the resume left on the stack
407  lua_pop(T_, numres);
408  // Set "valid" to false on the now-expired context and info tables
409  lua_pushstring(owner_.get_state(), "valid");
410  lua_pushboolean(owner_.get_state(), false);
411  lua_settable(owner_.get_state(), -3);
412  lua_pushstring(owner_.get_state(), "valid");
413  lua_pushboolean(owner_.get_state(), false);
414  lua_settable(owner_.get_state(), -4);
415  lua_pop(owner_.get_state(), 2);
416 
418 
419  for (const plugins_manager::event & req : this_context_backend->requests) {
420  if(ctxt.execute_kernel_ && req.name == "execute") {
421  results.push_back([this, lk = ctxt.execute_kernel_, data = req.data]() {
422  auto result = lk->run_binary_lua_tag(data);
423  int ref = result["ref"].to_int();
424  if(auto func = result.optional_child("executed")) {
425  lua_rawgeti(T_, LUA_REGISTRYINDEX, ref);
426  luaW_copy_upvalues(T_, *func);
427  luaL_unref(T_, LUA_REGISTRYINDEX, ref);
428  lua_pop(T_, 1);
429  }
430  result.remove_children("executed");
431  result.remove_attribute("ref");
432  plugins_manager::get()->notify_event(result["name"], result);
433  return true;
434  });
435  continue;
436  }
437  results.push_back(std::bind(ctxt.callbacks_.find(req.name)->second, req.data));
438  //results.emplace_back(ctxt.callbacks_.find(req.name)->second, req.data);
439  }
440  return results;
441 }
442 
443 bool luaW_copy_upvalues(lua_State* L, const config& cfg)
444 {
445  if(auto upvalues = cfg.optional_child("upvalues")) {
446  lua_pushvalue(L, -1); // duplicate function because lua_getinfo will pop it
447  lua_Debug info;
448  lua_getinfo(L, ">u", &info);
449  int funcindex = lua_absindex(L, -1);
450  for(int i = 1; i <= info.nups; i++, lua_pop(L, 1)) {
451  std::string_view name = lua_getupvalue(L, funcindex, i);
452  if(name == "_ENV") {
453  lua_pushglobaltable(L);
454  } else if(upvalues->has_attribute(name)) {
455  luaW_pushscalar(L, (*upvalues)[name]);
456  } else if(upvalues->has_child(name)) {
457  const auto& child = upvalues->mandatory_child(name);
458  if(child["upvalue_type"] == "array") {
459  auto children = upvalues->child_range(name);
460  lua_createtable(L, children.size(), 0);
461  for(const auto& cfg : children) {
462  luaW_pushscalar(L, cfg["value"]);
463  lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
464  }
465  } else if(child["upvalue_type"] == "named tuple") {
466  auto children = upvalues->child_range(name);
467  std::vector<std::string> names;
468  for(const auto& cfg : children) {
469  names.push_back(cfg["name"]);
470  }
472  for(const auto& cfg : children) {
473  luaW_pushscalar(L, cfg["value"]);
474  lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
475  }
476  } else if(child["upvalue_type"] == "config") {
477  luaW_pushconfig(L, child);
478  } else if(child["upvalue_type"] == "function") {
479  luaW_copy_upvalues(L, child);
480  lua_pushvalue(L, -1);
481  }
482  } else continue;
483  lua_setupvalue(L, funcindex, i);
484  }
485  }
486  return true;
487 }
GAME_CONFIG_SETTER("debug", bool, application_lua_kernel)
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 int intf_execute(lua_State *L)
bool luaW_copy_upvalues(lua_State *L, const config &cfg)
static char * v_threadtableKey
#define LOG_LUA
static void *const threadtableKey
static int intf_delay(lua_State *L)
static auto & dummy
#define DBG_LUA
luaW_Registry & gameConfigReg()
std::vector< std::string > names
Definition: build_info.cpp:67
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:158
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:384
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:71
callback_list callbacks_
Definition: context.hpp:69
lua_kernel_base * execute_kernel_
Definition: context.hpp:72
accessor_list accessors_
Definition: context.hpp:70
std::function< config(config)> accessor_function
Definition: context.hpp:37
std::size_t size()
Definition: manager.cpp:68
void notify_event(const std::string &name, const config &data)
Definition: manager.cpp:144
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:905
void luaW_push_namedtuple(lua_State *L, const std::vector< std::string > &names)
Push an empty "named tuple" onto the stack.
Definition: lua_common.cpp:763
config luaW_checkconfig(lua_State *L, int index)
Converts an optional table or vconfig to a config object.
void luaW_pushscalar(lua_State *L, const config::attribute_value &v)
Converts an attribute value into a Lua object pushed at the top of the stack.
Definition: lua_common.cpp:578
bool luaW_tableget(lua_State *L, int index, const char *key)
bool luaW_toconfig(lua_State *L, int index, config &cfg)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:934
bool luaW_getglobal(lua_State *L, const std::vector< std::string > &path)
Pushes the value found by following the variadic names (char *), if the value is not nil.
config luaW_serialize_function(lua_State *L, int func)
void line(int from_x, int from_y, int to_x, int to_y)
Draw a line.
Definition: draw.cpp:187
Handling of system events.
void set_debug(bool new_debug)
Definition: game_config.cpp:97
logger & info()
Definition: log.cpp:319
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
constexpr auto keys
Definition: ranges.hpp:39
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
std::decay_t< T > luaW_table_get_def(lua_State *L, int index, std::string_view k, const T &def)
returns t[k] where k is the table at index index and k is k or def if it is not convertible to the co...
Definition: push_check.hpp:435
Error used to report an error in a lua script or in the lua interpreter.
Definition: game_errors.hpp:54
Holds a lookup table for members of one type of object.
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
#define e