123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611 |
- /*
- * 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 "vcmi/Services.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
|