1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-28 23:06:24 +02:00
vcmi/scripting/lua/LuaScriptingContext.cpp

611 lines
11 KiB
C++

/*
* LuaScriptingContext.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "LuaScriptingContext.h"
#include <vstd/StringUtils.h>
#include <vcmi/events/EventBus.h>
#include <vcmi/ServerCallback.h>
#include "LuaStack.h"
#include "api/Registry.h"
#include "../../lib/json/JsonNode.h"
#include "../../lib/filesystem/Filesystem.h"
#include "../../lib/battle/IBattleInfoCallback.h"
#include "../../lib/CGameInfoCallback.h"
#include "../../lib/modding/ModScope.h"
VCMI_LIB_NAMESPACE_BEGIN
namespace scripting
{
const std::string LuaContext::STATE_FIELD = "DATA";
LuaContext::LuaContext(const Script * source, const Environment * env_):
ContextBase(env_->logger()),
L(luaL_newstate()),
script(source),
env(env_)
{
static const std::vector<luaL_Reg> STD_LIBS =
{
{"", luaopen_base},
{LUA_TABLIBNAME, luaopen_table},
{LUA_STRLIBNAME, luaopen_string},
{LUA_MATHLIBNAME, luaopen_math},
{LUA_BITLIBNAME, luaopen_bit}
};
for(const luaL_Reg & lib : STD_LIBS)
{
lua_pushcfunction(L, lib.func);
lua_pushstring(L, lib.name);
lua_call(L, 1, 0);
}
popAll();
cleanupGlobals();
popAll();
lua_newtable(L);
modules = std::make_shared<LuaReference>(L);
popAll();
registerCore();
popAll();
LuaStack S(L);
S.push(env->game());
lua_setglobal(L, "GAME");
S.push(env->battle(BattleID::NONE));
lua_setglobal(L, "BATTLE");
S.push(env->eventBus());
lua_setglobal(L, "EVENT_BUS");
S.push(env->services());
lua_setglobal(L, "SERVICES");
popAll();
}
LuaContext::~LuaContext()
{
modules.reset();
scriptClosure.reset();
lua_close(L);
}
void LuaContext::cleanupGlobals()
{
LuaStack S(L);
S.clear();
S.pushNil();
lua_setglobal(L, "collectgarbage");
S.pushNil();
lua_setglobal(L, "dofile");
S.pushNil();
lua_setglobal(L, "load");
S.pushNil();
lua_setglobal(L, "loadfile");
S.pushNil();
lua_setglobal(L, "loadstring");
S.pushNil();
lua_setglobal(L, "print");
S.clear();
lua_getglobal(L, LUA_STRLIBNAME);
S.push("dump");
S.pushNil();
lua_rawset(L, -3);
S.clear();
lua_getglobal(L, LUA_MATHLIBNAME);
S.push("random");
S.pushNil();
lua_rawset(L, -3);
S.push("randomseed");
S.pushNil();
lua_rawset(L, -3);
S.clear();
}
void LuaContext::run(ServerCallback * server, const JsonNode & initialState)
{
{
LuaStack S(L);
S.push(server);
lua_setglobal(L, "SERVER");
S.clear();
}
run(initialState);
// {
// LuaStack S(L);
// S.pushNil();
// lua_setglobal(L, "SERVER");
// S.clear();
// }
}
void LuaContext::run(const JsonNode & initialState)
{
setGlobal(STATE_FIELD, initialState);
int ret = luaL_loadbuffer(L, script->getSource().c_str(), script->getSource().size(), script->getName().c_str());
if(ret)
{
logger->error("Script '%s' failed to load, error: %s", script->getName(), toStringRaw(-1));
popAll();
return;
}
scriptClosure = std::make_shared<LuaReference>(L);
popAll();
scriptClosure->push();
ret = lua_pcall(L, 0, 0, 0);
if(ret)
{
logger->error("Script '%s' failed to run, error: '%s'", script->getName(), toStringRaw(-1));
popAll();
}
}
int LuaContext::errorRetVoid(const std::string & message)
{
logger->error(message);
popAll();
return 0;
}
JsonNode LuaContext::callGlobal(const std::string & name, const JsonNode & parameters)
{
LuaStack S(L);
lua_getglobal(L, name.c_str());
if(!S.isFunction(-1))
{
boost::format fmt("%s is not a function");
fmt % name;
logger->error(fmt.str());
S.clear();
return JsonNode();
}
int argc = parameters.Vector().size();
for(int idx = 0; idx < argc; idx++)
S.push(parameters.Vector()[idx]);
if(lua_pcall(L, argc, 1, 0))
{
std::string error = lua_tostring(L, -1);
boost::format fmt("Lua function %s failed with message: %s");
fmt % name % error;
logger->error(fmt.str());
S.clear();
return JsonNode();
}
JsonNode ret;
pop(ret);
S.balance();
return ret;
}
JsonNode LuaContext::callGlobal(ServerCallback * cb, const std::string & name, const JsonNode & parameters)
{
LuaStack S(L);
S.push(cb);
lua_setglobal(L, "SERVER");
auto ret = callGlobal(name, parameters);
S.pushNil();
lua_setglobal(L, "SERVER");
return ret;
}
void LuaContext::getGlobal(const std::string & name, int & value)
{
LuaStack S(L);
lua_getglobal(L, name.c_str());
lua_Integer temp;
if(S.tryGetInteger(-1, temp))
value = static_cast<int>(temp);
else
value = 0;
S.balance();
}
void LuaContext::getGlobal(const std::string & name, std::string & value)
{
LuaStack S(L);
lua_getglobal(L, name.c_str());
if(!S.tryGet(-1, value))
value.clear();
S.balance();
}
void LuaContext::getGlobal(const std::string & name, double & value)
{
LuaStack S(L);
lua_getglobal(L, name.c_str());
if(!S.tryGet(-1, value))
value = 0.0;
S.balance();
}
void LuaContext::getGlobal(const std::string & name, JsonNode & value)
{
LuaStack S(L);
lua_getglobal(L, name.c_str());
pop(value);
S.balance();
}
void LuaContext::setGlobal(const std::string & name, int value)
{
lua_pushinteger(L, static_cast<lua_Integer>(value));
lua_setglobal(L, name.c_str());
}
void LuaContext::setGlobal(const std::string & name, const std::string & value)
{
lua_pushlstring(L, value.c_str(), value.size());
lua_setglobal(L, name.c_str());
}
void LuaContext::setGlobal(const std::string & name, double value)
{
lua_pushnumber(L, value);
lua_setglobal(L, name.c_str());
}
void LuaContext::setGlobal(const std::string & name, const JsonNode & value)
{
LuaStack S(L);
S.push(value);
lua_setglobal(L, name.c_str());
S.balance();
}
JsonNode LuaContext::saveState()
{
JsonNode data;
getGlobal(STATE_FIELD, data);
return data;
}
void LuaContext::pop(JsonNode & value)
{
auto type = lua_type(L, -1);
switch(type)
{
case LUA_TNUMBER:
value.Float() = lua_tonumber(L, -1);
break;
case LUA_TBOOLEAN:
value.Bool() = (lua_toboolean(L, -1) != 0);
break;
case LUA_TSTRING:
value.String() = toStringRaw(-1);
break;
case LUA_TTABLE:
{
JsonNode asVector;
JsonNode asStruct;
lua_pushnil(L); /* first key */
while(lua_next(L, -2) != 0)
{
/* 'key' (at index -2) and 'value' (at index -1) */
JsonNode fieldValue;
pop(fieldValue);
if(lua_type(L, -1) == LUA_TNUMBER)
{
auto key = lua_tointeger(L, -1);
if(key > 0)
{
if(asVector.Vector().size() < key)
asVector.Vector().resize(key);
--key;
asVector.Vector().at(key) = fieldValue;
}
}
else if(lua_isstring(L, -1))
{
auto key = toStringRaw(-1);
asStruct[key] = fieldValue;
}
}
if(!asVector.Vector().empty())
{
std::swap(value, asVector);
}
else
{
std::swap(value, asStruct);
}
}
break;
default:
value.clear();
break;
}
lua_pop(L, 1);
}
void LuaContext::push(const std::string & value)
{
lua_pushlstring(L, value.c_str(), value.size());
}
void LuaContext::push(lua_CFunction f, void * opaque)
{
lua_pushlightuserdata(L, opaque);
lua_pushcclosure(L, f, 1);
}
void LuaContext::popAll()
{
lua_settop(L, 0);
}
std::string LuaContext::toStringRaw(int index)
{
size_t len = 0;
const auto *raw = lua_tolstring(L, index, &len);
return std::string(raw, len);
}
void LuaContext::registerCore()
{
push(&LuaContext::require, this);
lua_setglobal(L, "require");
push(&LuaContext::logError, this);
lua_setglobal(L, "logError");
popAll();//just in case
for(const auto & registar : api::Registry::get()->getCoreData())
{
registar.second->pushMetatable(L); //table
modules->push(); //table modules
push(registar.first); //table modules name
lua_pushvalue(L, -3); //table modules name table
lua_rawset(L, -3);
popAll();
}
}
int LuaContext::require(lua_State * L)
{
auto * self = static_cast<LuaContext *>(lua_touserdata(L, lua_upvalueindex(1)));
if(!self)
{
lua_pushstring(L, "internal error");
lua_error(L);
return 0;
}
return self->loadModule();
}
int LuaContext::loadModule()
{
int argc = lua_gettop(L);
if(argc < 1)
return errorRetVoid("Module name required");
//if module is loaded already, assume that module name is valid
modules->push();
lua_pushvalue(L, -2);
lua_rawget(L, -2);
if(lua_istable(L, -1))
{
lua_replace(L, 1);
lua_settop(L, 1);
return 1;
}
//continue with more checks
if(!lua_isstring(L, 1))
return errorRetVoid("Module name must be string");
std::string resourceName = toStringRaw(1);
if(resourceName.empty())
return errorRetVoid("Module name is empty");
auto temp = vstd::split(resourceName, ":");
std::string scope;
std::string modulePath;
if(temp.size() <= 1)
{
modulePath = temp.at(0);
}
else
{
scope = temp.at(0);
modulePath = temp.at(1);
}
if(scope.empty())
{
const auto *registar = api::Registry::get()->find(modulePath);
if(!registar)
{
return errorRetVoid("Module not found: "+modulePath);
}
registar->pushMetatable(L);
}
else if(scope == ModScope::scopeBuiltin())
{
// boost::algorithm::replace_all(modulePath, boost::is_any_of("\\/ "), "");
boost::algorithm::replace_all(modulePath, ".", "/");
auto *loader = CResourceHandler::get(ModScope::scopeBuiltin());
modulePath = "scripts/lib/" + modulePath;
ResourcePath id(modulePath, EResType::LUA);
if(!loader->existsResource(id))
return errorRetVoid("Module not found: "+modulePath);
auto rawData = loader->load(id)->readAll();
auto sourceText = std::string(reinterpret_cast<char *>(rawData.first.get()), rawData.second);
int ret = luaL_loadbuffer(L, sourceText.c_str(), sourceText.size(), modulePath.c_str());
if(ret)
return errorRetVoid(toStringRaw(-1));
ret = lua_pcall(L, 0, 1, 0);
if(ret)
{
logger->error("Module '%s' failed to run, error: %s", modulePath, toStringRaw(-1));
popAll();
return 0;
}
}
else
{
//todo: also allow loading scripts from same scope
return errorRetVoid("No access to scope "+scope);
}
modules->push(); //name table modules
lua_pushvalue(L, 1);//name table modules name
if(!lua_isstring(L, -1))
return errorRetVoid("Module name corrupted");
lua_pushvalue(L, -3);//name table modules name table
lua_rawset(L, -3);//name table modules
lua_pop(L, 1);//name table
lua_replace(L, 1);//table table
lua_settop(L, 1);//table
return 1;
}
int LuaContext::print(lua_State * L)
{
//TODO:
lua_settop(L, 0);
return 0;
}
int LuaContext::printImpl()
{
//TODO:
return 0;
}
int LuaContext::logError(lua_State * L)
{
auto * self = static_cast<LuaContext *>(lua_touserdata(L, lua_upvalueindex(1)));
if(!self)
{
lua_pushstring(L, "internal error");
lua_error(L);
return 0;
}
return self->logErrorImpl();
}
int LuaContext::logErrorImpl()
{
LuaStack S(L);
std::string message;
if(S.tryGet(1, message))
logger->error(message);
return S.retVoid();
}
}
VCMI_LIB_NAMESPACE_END