wesnoth/src/scripting/application_lua_kernel.cpp
gfgtdf b892e16b94 include lua/... files after others
headers from lua/..  contain macros that can break other headers in
wesnoth, boost or other libraries.

In this case it was a macro
#define cast(t, exp) ((t)(exp))
defined in lua/llimits.h that broke a boost header.
2015-03-27 00:54:22 +01:00

331 lines
10 KiB
C++

/*
Copyright (C) 2014 - 2015 by Chris Beck <render787@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/**
* @file
* Provides a Lua interpreter, to drive the game_controller.
*
* @note Naming conventions:
* - intf_ functions are exported in the wesnoth domain,
* - impl_ functions are hidden inside metatables,
* - cfun_ functions are closures,
* - luaW_ functions are helpers in Lua style.
*/
#include "scripting/application_lua_kernel.hpp"
#include "global.hpp"
#include "config.hpp"
#include "game_errors.hpp"
#include "log.hpp"
#include "scripting/lua_api.hpp"
#include "scripting/lua_common.hpp"
#include "scripting/lua_cpp_function.hpp"
#include "scripting/lua_fileops.hpp"
#include "scripting/lua_kernel_base.hpp"
#include "scripting/lua_types.hpp"
#include "scripting/plugins/context.hpp"
#include "scripting/plugins/manager.hpp"
#ifdef DEBUG_LUA
#include "scripting/debug_lua.hpp"
#endif
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
#include <boost/make_shared.hpp>
#include <boost/noncopyable.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/shared_ptr.hpp>
#include "lua/lauxlib.h"
#include "lua/lua.h"
#include "lua/luaconf.h"
class CVideo;
struct lua_State;
static lg::log_domain log_scripting_lua("scripting/lua");
#define DBG_LUA LOG_STREAM(debug, log_scripting_lua)
#define LOG_LUA LOG_STREAM(info, log_scripting_lua)
#define WRN_LUA LOG_STREAM(warn, log_scripting_lua)
#define ERR_LUA LOG_STREAM(err, log_scripting_lua)
static int intf_describe_plugins(lua_State * L)
{
std::cerr << "describe plugins (" << plugins_manager::get()->size() << "):\n";
lua_getglobal(L, "print");
for (size_t i = 0; i < plugins_manager::get()->size(); ++i) {
lua_pushvalue(L,-1); //duplicate the print
std::stringstream line;
line << i
<< ":\t"
<< plugins_manager::get()->get_status(i)
<< "\t\t"
<< plugins_manager::get()->get_name(i)
<< "\n";
DBG_LUA << line.str();
lua_pushstring(L, line.str().c_str());
lua_call(L, 1, 0);
}
if (!plugins_manager::get()->size()) {
lua_pushstring(L, "No plugins available.\n");
lua_call(L, 1, 0);
}
return 0;
}
application_lua_kernel::application_lua_kernel(CVideo * ptr)
: lua_kernel_base(ptr)
{
lua_pushcfunction(mState, &intf_describe_plugins);
lua_setglobal(mState, "describe_plugins");
lua_settop(mState, 0);
}
application_lua_kernel::thread::thread(lua_State * T) : T_(T), started_(false) {}
std::string application_lua_kernel::thread::status()
{
if (!started_) {
if (lua_status(T_) == LUA_OK) {
return "not started";
} else {
return "load error";
}
}
switch (lua_status(T_)) {
case LUA_OK:
return "dead";
case LUA_YIELD:
return "started";
default:
return "error";
}
}
bool application_lua_kernel::thread::is_running() {
return started_ ? (lua_status(T_) == LUA_YIELD) : (lua_status(T_) == LUA_OK);
}
static char * v_threadtableKey = 0;
static void * const threadtableKey = static_cast<void *> (& v_threadtableKey);
static lua_State * get_new_thread(lua_State * L)
{
lua_pushlightuserdata(L , threadtableKey);
lua_pushvalue(L,1); // duplicate script key, since we need to store later
// stack is now [script key] [script key]
lua_rawget(L, LUA_REGISTRYINDEX); // get the script table from the registry, on the top of the stack
if (!lua_istable(L,-1)) { // if it doesn't exist create it
lua_pop(L,1);
lua_newtable(L);
} // stack is now [script key] [table]
lua_pushinteger(L, lua_objlen(L, -1) + 1); // push #table + 1 onto the stack
lua_State * T = lua_newthread(L); // create new thread T
// stack is now [script key] [table] [#table + 1] [thread]
lua_rawset(L, -3); // store the new thread at #table +1 index of the table.
// stack is now [script key] [table]
lua_rawset(L, LUA_REGISTRYINDEX);
// stack L is now empty
return T; // now we can set up T's stack appropriately
}
application_lua_kernel::thread * application_lua_kernel::load_script_from_string(const std::string & prog)
{
lua_State * T = get_new_thread(mState);
// now we are operating on T's stack, leaving a compiled C function on it.
DBG_LUA << "created thread: status = " << lua_status(T) << (lua_status(T) == LUA_OK ? " == OK" : " == ?") << "\n";
DBG_LUA << "loading script from string:\n<<\n" << prog << "\n>>\n";
int errcode = luaL_loadstring(T, prog.c_str());
if (errcode != LUA_OK) {
const char * err_str = lua_tostring(T, -1);
std::string msg = err_str ? err_str : "null string";
std::string context = "When parsing a string to a lua thread, ";
if (errcode == LUA_ERRSYNTAX) {
context += " a syntax error";
} else if(errcode == LUA_ERRMEM){
context += " a memory error";
} else if(errcode == LUA_ERRGCMM) {
context += " an error in garbage collection metamethod";
} else {
context += " an unknown error";
}
throw game::lua_error(msg, context);
}
if (!lua_kernel_base::protected_call(T, 0, 1, boost::bind(&lua_kernel_base::log_error, this, _1, _2))) {
throw game::lua_error("Error when executing a script to make a lua thread.");
}
if (!lua_isfunction(T, -1)) {
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)) );
}
return new application_lua_kernel::thread(T);
}
application_lua_kernel::thread * application_lua_kernel::load_script_from_file(const std::string & file)
{
lua_State * T = get_new_thread(mState);
// now we are operating on T's stack, leaving a compiled C function on it.
lua_pushstring(T, file.c_str());
lua_fileops::load_file(T);
if (!lua_kernel_base::protected_call(T, 0, 1, boost::bind(&lua_kernel_base::log_error, this, _1, _2))) {
throw game::lua_error("Error when executing a file to make a lua thread.");
}
if (!lua_isfunction(T, -1)) {
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)) );
}
return new application_lua_kernel::thread(T);
}
struct lua_context_backend {
std::vector<plugins_manager::event> requests;
bool valid;
lua_context_backend()
: requests()
, valid(true)
{}
};
static int impl_context_backend(lua_State * L, boost::shared_ptr<lua_context_backend> backend, std::string req_name)
{
if (!backend->valid) {
luaL_error(L , "Error, you tried to use an invalid context object in a lua thread");
}
plugins_manager::event evt;
evt.name = req_name;
evt.data = luaW_checkconfig(L, -1);
backend->requests.push_back(evt);
return 0;
}
static int impl_context_accessor(lua_State * L, boost::shared_ptr<lua_context_backend> backend, plugins_context::accessor_function func)
{
if (!backend->valid) {
luaL_error(L , "Error, you tried to use an invalid context object in a lua thread");
}
if(lua_gettop(L)) {
config temp;
if(!luaW_toconfig(L, 1, temp)) {
luaL_argerror(L, 1, "Error, tried to parse a config but some fields were invalid");
}
luaW_pushconfig(L, func(temp));
return 1;
} else {
luaW_pushconfig(L, func(config()));
return 1;
}
}
application_lua_kernel::request_list application_lua_kernel::thread::run_script(const plugins_context & ctxt, const std::vector<plugins_manager::event> & queue)
{
// There are two possibilities: (1) this is the first execution, and the C function is the only thing on the stack
// (2) this is a subsequent execution, and there is nothing on the stack.
// Either way we push the arguments to the function and call resume.
// First we have to create the event table, by concatenating the event queue into a table.
lua_newtable(T_); //this will be the event table
for (size_t i = 0; i < queue.size(); ++i) {
lua_newtable(T_);
lua_pushstring(T_, queue[i].name.c_str());
lua_rawseti(T_, -2, 1);
luaW_pushconfig(T_, queue[i].data);
lua_rawseti(T_, -2, 2);
lua_rawseti(T_, -2, i+1);
}
// Now we have to create the context object. It is arranged as a table of boost functions.
boost::shared_ptr<lua_context_backend> this_context_backend = boost::make_shared<lua_context_backend> (lua_context_backend());
lua_newtable(T_); // this will be the context table
BOOST_FOREACH( const std::string & key, ctxt.callbacks_ | boost::adaptors::map_keys ) {
lua_pushstring(T_, key.c_str());
lua_cpp::push_function(T_, boost::bind(&impl_context_backend, _1, this_context_backend, key));
lua_settable(T_, -3);
}
// Now we have to create the info object (context accessors). It is arranged as a table of boost functions.
lua_newtable(T_); // this will be the info table
lua_pushstring(T_, "name");
lua_pushstring(T_, ctxt.name_.c_str());
lua_settable(T_, -3);
BOOST_FOREACH( const plugins_context::accessor_list::value_type & v, ctxt.accessors_) {
const std::string & key = v.first;
const plugins_context::accessor_function & func = v.second;
lua_pushstring(T_, key.c_str());
lua_cpp::push_function(T_, boost::bind(&impl_context_accessor, _1, this_context_backend, func));
lua_settable(T_, -3);
}
// Now we resume the function, calling the coroutine with the three arguments (events, context, info).
lua_resume(T_, NULL, 3);
started_ = true;
this_context_backend->valid = false; //invalidate the context object for lua
if (lua_status(T_) != LUA_YIELD) {
LOG_LUA << "Thread status = '" << lua_status(T_) << "'\n";
if (lua_status(T_) != LUA_OK) {
std::stringstream ss;
ss << "encountered a";
switch(lua_status(T_)) {
case LUA_ERRSYNTAX:
ss << " syntax ";
break;
case LUA_ERRRUN:
ss << " runtime ";
break;
case LUA_ERRERR:
ss << " error-handler ";
break;
default:
ss << " ";
break;
}
ss << "error:\n" << lua_tostring(T_, -1) << "\n";
ERR_LUA << ss.str() << std::endl;
}
}
application_lua_kernel::request_list results;
BOOST_FOREACH( const plugins_manager::event & req, this_context_backend->requests) {
results.push_back(boost::bind(ctxt.callbacks_.find(req.name)->second, req.data));
//results.push_back(std::make_pair(ctxt.callbacks_.find(req.name)->second, req.data));
}
return results;
}