| 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 "LuaStack.h"#include "api/Registry.h"#include "../../lib/JsonNode.h"#include "../../lib/NetPacks.h"#include "../../lib/filesystem/Filesystem.h"#include "../../lib/battle/IBattleInfoCallback.h"#include "../../lib/CGameInfoCallback.h"VCMI_LIB_NAMESPACE_BEGINnamespace scripting{const std::string LuaContext::STATE_FIELD = "DATA";LuaContext::LuaContext(const Script * source, const Environment * env_)	: ContextBase(env_->logger()),	script(source),	env(env_){	L = luaL_newstate();	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());	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 std::move(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::JsonType::DATA_VECTOR);			JsonNode asStruct(JsonNode::JsonType::DATA_STRUCT);			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;	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(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){	LuaContext * 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())	{		auto registar = api::Registry::get()->find(modulePath);		if(!registar)		{			return errorRetVoid("Module not found: "+modulePath);		}		registar->pushMetatable(L);	}	else if(scope == CModHandler::scopeBuiltin())	{	//	boost::algorithm::replace_all(modulePath, boost::is_any_of("\\/ "), "");		boost::algorithm::replace_all(modulePath, ".", "/");		auto loader = CResourceHandler::get(CModHandler::scopeBuiltin());		modulePath = "scripts/lib/" + modulePath;		ResourceID id(modulePath, EResType::LUA);		if(!loader->existsResource(id))			return errorRetVoid("Module not found: "+modulePath);		auto rawData = loader->load(id)->readAll();		auto sourceText = std::string((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){	LuaContext * 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
 |