The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
application_lua_kernel.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2014 - 2025
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"
42#include "utils/ranges.hpp"
43
44#ifdef DEBUG_LUA
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
58static 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
64static 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
91static 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
97static int intf_execute(lua_State* L);
98
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
124application_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
149static char * v_threadtableKey = nullptr;
150static void * const threadtableKey = static_cast<void *> (& v_threadtableKey);
151
152static 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
239static 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
253static 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
273static auto& dummy = gameConfigReg(); // just to ensure it's constructed.
274
279
280static 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"].to_int() != 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));
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}
320bool luaW_copy_upvalues(lua_State* L, const config& cfg);
321application_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.
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
443bool 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}
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 lua_State * get_new_thread(lua_State *L)
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
static void *const threadtableKey
static int intf_delay(lua_State *L)
static auto & dummy
#define DBG_LUA
luaW_Registry & gameConfigReg()
#define ERR_LUA
#define LOG_LUA
std::vector< std::string > names
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:157
optional_config_impl< config > optional_child(std::string_view key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition config.cpp:380
virtual void log_error(char const *msg, char const *context="Lua error")
Error reporting mechanisms, used by virtual methods protected_call and load_string.
bool protected_call(int nArgs, int nRets, const error_handler &)
Efficiently creates new LUA "named tuples" with the specified field names.
void push(lua_State *L) const
Push an empty "named tuple" onto the stack.
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).
const config * cfg
Standard logging facilities (interface).
#define PLAIN_LOG
Definition log.hpp:296
void luaW_pushconfig(lua_State *L, const config &cfg)
Converts a config object to a Lua table pushed at the top of the stack.
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.
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.
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.
#define DBG_LUA
config luaW_serialize_function(lua_State *L, int func)
#define GAME_CONFIG_SETTER(name, type, kernel_type)
Handling of system events.
void set_debug(bool new_debug)
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)
constexpr auto keys
Definition ranges.hpp:43
std::string_view data
Definition picture.cpp:188
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...
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
Error used to report an error in a lua script or in the lua interpreter.
Holds a lookup table for members of one type of object.
std::vector< plugins_manager::event > requests
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition enum_base.hpp:46
#define i
#define e