// Ryzom - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 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; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "stdpch.h"
#include "nel/gui/lua_object.h"
#include "nel/gui/lua_ihm.h"
#include "nel/gui/lua_helper.h"
namespace NLGUI
{
// *************************************************
CLuaObject::CLuaObject(CLuaState &state, const char *id)
{
pop(state, id);
}
// *************************************************
CLuaObject::CLuaObject(CLuaState &state, const std::string &id)
{
pop(state, id.c_str());
}
// *************************************************
bool CLuaObject::isValid() const
{
return getLuaState() != NULL;
}
// *************************************************
CLuaState *CLuaObject::getLuaState() const
{
return _LuaState;
}
// *************************************************
CLuaObject::CLuaObject(const CLuaObject &other)
{
if (other.isValid())
{
other.push();
nlassert(other.getLuaState());
pop(*other.getLuaState());
_Id = other._Id;
}
// else ... copy of an invalid CLuaObject( is an invalid CLuaObject
}
// *************************************************
CLuaObject &CLuaObject::operator=(const CLuaObject &other)
{
if (!other.isValid())
{
release();
return *this;
}
other.push();
pop(*other.getLuaState());
_Id = other._Id;
return *this;
}
// *************************************************
bool CLuaObject::rawEqual(const CLuaObject &other) const
{
nlassert(other.getLuaState() == getLuaState());
push();
other.push();
bool equal = other.getLuaState()->rawEqual(-1, -2);
getLuaState()->pop(2);
return equal;
}
// *************************************************
CLuaObject::~CLuaObject()
{
release();
}
// *************************************************
void CLuaObject::release()
{
if (_LuaState)
{
CLuaStackChecker lsc(_LuaState);
_LuaState->pushLightUserData((void *) this);
_LuaState->pushNil();
_LuaState->setTable(LUA_REGISTRYINDEX);
_LuaState = NULL;
}
}
// *************************************************
void CLuaObject::pop(CLuaState &luaState, const char *id)
{
release();
nlassert(luaState.getTop() >= 1);
{
CLuaStackChecker lsc(&luaState);
luaState.pushLightUserData((void *) this);
luaState.pushValue(-2); // copy original object
luaState.setTable(LUA_REGISTRYINDEX);
}
luaState.pop();
_LuaState = &luaState;
_Id = id;
}
// *************************************************
void CLuaObject::push() const
{
nlassert(isValid());
_LuaState->pushLightUserData((void *) this);
_LuaState->getTable(LUA_REGISTRYINDEX);
}
// *************************************************
int CLuaObject::type() const
{
push();
int type = _LuaState->type();
_LuaState->pop();
return type;
}
// *************************************************
const char *CLuaObject::getTypename() const
{
push();
const char *typeName = _LuaState->getTypename(-1);
_LuaState->pop();
return typeName;
}
// *************************************************
bool CLuaObject::isNil() const { push(); bool result = _LuaState->isNil(); _LuaState->pop(); return result; }
bool CLuaObject::isNumber() const { push(); bool result = _LuaState->isNumber(); _LuaState->pop(); return result; }
bool CLuaObject::isBoolean() const { push(); bool result = _LuaState->isBoolean(); _LuaState->pop(); return result; }
bool CLuaObject::isString() const { push(); bool result = _LuaState->isString(); _LuaState->pop(); return result; }
bool CLuaObject::isFunction() const { push(); bool result = _LuaState->isFunction(); _LuaState->pop(); return result; }
bool CLuaObject::isCFunction() const { push(); bool result = _LuaState->isCFunction(); _LuaState->pop(); return result; }
bool CLuaObject::isTable() const { push(); bool result = _LuaState->isTable(); _LuaState->pop(); return result; }
bool CLuaObject::isUserData() const { push(); bool result = _LuaState->isUserData(); _LuaState->pop(); return result; }
bool CLuaObject::isLightUserData() const { push(); bool result = _LuaState->isLightUserData(); _LuaState->pop(); return result; }
bool CLuaObject::isRGBA() const
{
if (!isUserData()) return false;
push();
NLMISC::CRGBA dummy;
return CLuaIHM::pop(*_LuaState, dummy);
}
// *************************************************
bool CLuaObject::toBoolean() const { push(); bool result = _LuaState->toBoolean(); _LuaState->pop(); return result; }
lua_Number CLuaObject::toNumber() const { push(); lua_Number result = _LuaState->toNumber(); _LuaState->pop(); return result; }
std::string CLuaObject::toString() const
{
push();
const char *str = _LuaState->toString();
std::string result = str ? str : "";
_LuaState->pop();
return result;
}
lua_CFunction CLuaObject::toCFunction() const { push(); lua_CFunction result = _LuaState->toCFunction(); _LuaState->pop(); return result; }
void *CLuaObject::toUserData() const { push(); void *result = _LuaState->toUserData(); _LuaState->pop(); return result; }
const void *CLuaObject::toPointer() const { push(); const void *result = _LuaState->toPointer(); _LuaState->pop(); return result; }
NLMISC::CRGBA CLuaObject::toRGBA() const
{
NLMISC::CRGBA result;
push();
if (CLuaIHM::pop(*_LuaState, result))
{
return result;
}
return NLMISC::CRGBA::Black;
}
// *************************************************
CLuaObject::operator bool() const { return toBoolean(); }
CLuaObject::operator float() const { return (float) toNumber(); }
CLuaObject::operator double() const { return (double) toNumber(); }
CLuaObject::operator std::string() const { return toString(); }
// *************************************************
bool CLuaObject::isEnumerable() const
{
if (isTable()) return true;
CLuaStackRestorer lsr(_LuaState, _LuaState->getTop());
push();
if (_LuaState->getMetaTable(-1))
{
_LuaState->remove(-2);
_LuaState->push("__next");
_LuaState->getTable(-2);
return _LuaState->isFunction();
}
return false;
}
// *************************************************
CLuaEnumeration CLuaObject::enumerate() throw(ELuaNotATable)
{
if (!isEnumerable())
{
throw ELuaNotATable(NLMISC::toString("Called CLuaObject::enumerate on an object that has no enumeration (not a table or no '__next' method in the metatable). Object is '%s' with type '%s'", getId().c_str(), getTypename()).c_str());
}
return CLuaEnumeration(*this);
}
// *************************************************
CLuaObject CLuaObject::operator[](const char *key) const
{
nlassert(key);
nlassert(isValid());
if (!isEnumerable())
{
_LuaState->pushNil();
CLuaObject result(*_LuaState);
return result;
}
CLuaStackChecker lsc(_LuaState);
push();
_LuaState->push(key);
_LuaState->getTable(-2);
CLuaObject subObject(*_LuaState, concatId(getId(), key));
_LuaState->pop(); // pop the sub object
return subObject;
}
// *************************************************
CLuaObject CLuaObject::operator[](double key) const
{
nlassert(isValid());
if (!isEnumerable())
{
_LuaState->pushNil();
CLuaObject result(*_LuaState);
return result;
}
CLuaStackChecker lsc(_LuaState);
push();
_LuaState->push(key);
_LuaState->getTable(-2);
CLuaObject subObject(*_LuaState, concatId(getId(), NLMISC::toString(key)));
_LuaState->pop(); // pop the sub object
return subObject;
}
// *************************************************
CLuaObject CLuaObject::at(const char *key) const throw(ELuaNotATable)
{
if (!isEnumerable()) throw ELuaNotATable(NLMISC::toString("Can't get key '%s' in object '%s' because type is '%s', it is not a table.", key, getId().c_str(), getTypename()).c_str());
return operator[](key);
}
// *************************************************
bool CLuaObject::hasKey(const char *key) const
{
if (!isEnumerable()) throw ELuaNotATable(NLMISC::toString("Trying to access key '%s' on object '%s' of type %s.", key, getId().c_str(), getTypename()).c_str());
CLuaObject value = operator[](key);
return (!value.isNil());
}
// *************************************************
CLuaObject CLuaObject::newTable(const char *tableName) throw(ELuaNotATable)
{
nlassert(tableName);
nlassert(isValid());
if (!isTable()) throw ELuaNotATable(NLMISC::toString("Trying to create a subtable '%s' on object '%s' of type %s (not a table).", tableName, getId().c_str(), getTypename()));
CLuaStackChecker lsc(_LuaState);
push();
_LuaState->push(tableName);
_LuaState->newTable();
_LuaState->setTable(-3);
_LuaState->pop();
return at(tableName); //\TODO nico double copy here ...
}
// *************************************************
void CLuaObject::setValue(const char *key, const CLuaObject &value) throw(ELuaNotATable)
{
nlassert(key);
nlassert(isValid());
nlassert(value.isValid());
nlassert(getLuaState() == value.getLuaState());
if (!isTable()) throw ELuaNotATable(NLMISC::toString("Trying to set a value '%s' on object '%s' of type %s (not a table).", key, getId().c_str(), getTypename()));
CLuaStackChecker lsc(_LuaState);
push();
_LuaState->push(key);
value.push();
_LuaState->setTable(-3);
_LuaState->pop();
}
// *************************************************
void CLuaObject::setNil(const char *key) throw(ELuaNotATable)
{
nlassert(key);
nlassert(isValid());
if (!isTable()) throw ELuaNotATable(NLMISC::toString("Trying to set a value 'nil' at key %s on object '%s' of type %s (not a table).", key, getId().c_str(), getTypename()));
CLuaStackChecker lsc(_LuaState);
push();
_LuaState->push(key);
_LuaState->pushNil();
_LuaState->setTable(-3);
_LuaState->pop();
}
// *************************************************
void CLuaObject::setValue(const char *key, const char *value) throw(ELuaNotATable)
{
nlassert(value);
nlassert(key);
nlassert(isValid());
if (!isTable()) throw ELuaNotATable(NLMISC::toString("Trying to set a value '%s' at key %s on object '%s' of type %s (not a table).", value, key, getId().c_str(), getTypename()));
CLuaStackChecker lsc(_LuaState);
push();
_LuaState->push(key);
_LuaState->push(value);
_LuaState->setTable(-3);
_LuaState->pop();
}
// *************************************************
void CLuaObject::setValue(const char *key, const std::string &value) throw(ELuaNotATable)
{
setValue(key, value.c_str());
}
// *************************************************
void CLuaObject::setValue(const char *key, bool value) throw(ELuaNotATable)
{
nlassert(key);
nlassert(isValid());
if (!isTable()) throw ELuaNotATable(NLMISC::toString("Trying to set a value '%s' at key %s on object '%s' of type %s (not a table).", value ? "true" : "false", key, getId().c_str(), getTypename()));
CLuaStackChecker lsc(_LuaState);
push();
_LuaState->push(key);
_LuaState->push(value);
_LuaState->setTable(-3);
_LuaState->pop();
}
// *************************************************
void CLuaObject::setValue(const char *key, TLuaWrappedFunction value) throw(ELuaNotATable)
{
nlassert(key);
nlassert(isValid());
if (!isTable()) throw ELuaNotATable(NLMISC::toString("Trying to set a function value '%p' at key %s on object '%s' of type %s (not a table).", (void *)value, key, getId().c_str(), getTypename()));
CLuaStackChecker lsc(_LuaState);
push();
_LuaState->push(key);
_LuaState->push(value);
_LuaState->setTable(-3);
_LuaState->pop();
}
// *************************************************
void CLuaObject::setValue(const char *key, double value) throw(ELuaNotATable)
{
nlassert(key);
nlassert(isValid());
if (!isTable()) throw ELuaNotATable(NLMISC::toString("Trying to set a value '%lf' at key %s on object '%s' of type %s (not a table).", value, key, getId().c_str(), getTypename()));
CLuaStackChecker lsc(_LuaState);
push();
_LuaState->push(key);
_LuaState->push(value);
_LuaState->setTable(-3);
_LuaState->pop();
}
// *************************************************
void CLuaObject::eraseValue(const char *key) throw(ELuaNotATable)
{
nlassert(isValid());
nlassert(key);
if (!isTable()) throw ELuaNotATable(NLMISC::toString("Trying to erase a value with key '%s' on object '%s' of type %s (not a table).", key, getId().c_str(), getTypename()));
CLuaStackChecker lsc(_LuaState);
push();
_LuaState->push(key);
_LuaState->pushNil();
_LuaState->setTable(-3);
_LuaState->pop();
}
// *************************************************
bool CLuaObject::callNoThrow(int numArgs, int numRet)
{
nlassert(isValid());
if (!isFunction())
{
nlwarning("Calling a non function object (id = %s, type = %s)", getId().c_str(), getTypename());
_LuaState->pop(numArgs);
return false;
}
// TMP TMP
static volatile bool dumpFunction = false;
if (dumpFunction)
{
CLuaStackRestorer lsr(_LuaState, _LuaState->getTop());
push();
lua_Debug ar;
lua_getinfo (_LuaState->getStatePointer(), ">lS", &ar);
nlwarning((std::string(ar.what) + ", at line " + NLMISC::toString(ar.linedefined) + " in " + std::string(ar.source)).c_str());
}
push();
_LuaState->insert(-1 - numArgs); // put the function before its arguments
int result = _LuaState->pcall(numArgs, numRet);
switch (result)
{
case LUA_ERRRUN:
case LUA_ERRMEM:
case LUA_ERRERR:
nlwarning(_LuaState->toString(-1));
_LuaState->pop();
return false;
break;
case 0:
return true;
break;
default:
nlassert(0);
break;
}
return false;
}
// *************************************************
bool CLuaObject::callMethodByNameNoThrow(const char *name, int numArgs, int numRet)
{
nlassert(isValid());
int initialStackSize = _LuaState->getTop();
if (!isTable() && !isUserData())
{
nlwarning("Can't call method : object is not a table (id = %s)", getId().c_str());
_LuaState->setTop(std::max(0, initialStackSize - numArgs));
return false;
}
CLuaObject method = (*this)[name];
push(); // the 'self' parameter
_LuaState->insert(-1 - numArgs); // put 'self' before the arguments
if (method.callNoThrow(numArgs + 1, numRet))
{
return true;
}
_LuaState->setTop(std::max(0, initialStackSize - numArgs));
return false;
}
/////////////////////
// CLuaEnumeration //
/////////////////////
// *************************************************
CLuaEnumeration::CLuaEnumeration(CLuaObject &table)
{
nlassert(table.isEnumerable());
CLuaState *luaState = table.getLuaState();
CLuaStackChecker lsc(luaState);
// get pointer to the 'next' function
#if LUA_VERSION_NUM >= 502
luaState->pushGlobalTable();
#else
luaState->pushValue(LUA_GLOBALSINDEX);
#endif
_NextFunction = CLuaObject(*luaState)["next"];
//
nlassert(luaState);
table.push();
luaState->pushNil();
_HasNext = false;
if (_NextFunction.callNoThrow(2, 2))
{
_Value.pop(*luaState);
_Key.pop(*luaState);
_HasNext = !_Key.isNil();
_Value.setId(CLuaObject::concatId(table.getId(), _Key.toString()));
_Table = table;
}
}
// *************************************************
const CLuaObject &CLuaEnumeration::nextKey() const
{
nlassert(_HasNext);
return _Key;
}
// *************************************************
CLuaObject &CLuaEnumeration::nextValue()
{
nlassert(_HasNext);
return _Value;
}
// *************************************************
CLuaObject CLuaObject::getMetaTable() const
{
nlassert(isValid());
CLuaStackChecker lsc(_LuaState);
push();
if (_LuaState->getMetaTable(-1))
{
_LuaState->remove(-2);
return CLuaObject(*_LuaState);
}
_LuaState->pop();
_LuaState->pushNil();
return CLuaObject(*_LuaState);
}
// *************************************************
bool CLuaObject::setMetaTable(CLuaObject &metatable)
{
nlassert(isValid());
nlassert(metatable.isValid());
nlassert(this->getLuaState() == metatable.getLuaState());
CLuaStackChecker lsc(_LuaState);
push();
metatable.push();
bool ok = _LuaState->setMetaTable(-2);
_LuaState->pop(1);
return ok;
}
// *************************************************
std::string CLuaObject::toStringRecurse(uint depth /*=0*/, uint maxDepth /*= 20*/, std::set *alreadySeen /*= NULL */) const
{
if (maxDepth != 0 && depth > maxDepth) return "";
const uint INDENT_NUM_BLANK = 2;
std::string indentStr(depth * INDENT_NUM_BLANK, ' ');
nlassert(_LuaState);
if (isEnumerable()) // is enumeration possible on that object ?
{
std::string result;
if (alreadySeen)
{
if (alreadySeen->count(toPointer())) // avoid cyclic graph (infinite recursion else)
{
result += indentStr +"";
return result;
}
alreadySeen->insert(toPointer());
}
result += indentStr + "{\n";
CLuaObject *table = const_cast(this);
uint numElem = 0;
ENUM_LUA_TABLE(*table, it)
{
//nlwarning("entering table %s", it.nextKey().toString().c_str());
result += std::string((depth + 1) * INDENT_NUM_BLANK, ' ') + it.nextKey().toString() + " = ";
if (it.nextValue().isEnumerable())
{
result += "\n" + it.nextValue().toStringRecurse(depth + 1, maxDepth, alreadySeen);
}
else
{
result += it.nextValue().toStringRecurse();
}
result += ",\n";
++ numElem;
if (numElem > 4000)
{
throw NLMISC::Exception("possible infinite loop, aborting enumeration");
}
}
result += indentStr + "}";
return result;
}
else if (isNil())
{
return (indentStr + "nil").c_str();
}
else if (isString())
{
return (indentStr + "\"" + toString() + "\"").c_str();
}
else if (isFunction())
{
return (indentStr + "").c_str();
}
else
{
return ((indentStr + toString()).c_str());
}
}
// *************************************************
void CLuaObject::dump(uint maxDepth /*= 20*/, std::set *alreadySeen /*= NULL */) const
{
try
{
std::string str = toStringRecurse(0, maxDepth, alreadySeen);
std::vector res;
NLMISC::explode(str, std::string("\n"), res);
for(uint k = 0; k < res.size(); ++k)
{
NLMISC::InfoLog->forceDisplayRaw((res[k] + "\n") .c_str());
}
}
catch(const std::exception &e)
{
//CLuaIHMRyzom::dumpCallStack();
nlwarning(e.what());
}
}
// *************************************************
std::string CLuaObject::concatId(const std::string &left,const std::string &right)
{
if (!right.empty() && isdigit(right[0]))
{
if (left.empty()) return "[" + right + "]";
return left + "[" + right + "]";
}
if (left.empty()) return right;
return left + "." + right;
}
// *************************************************
void CLuaEnumeration::next()
{
nlassert(_HasNext);
CLuaState *luaState = _Table.getLuaState();
nlassert(luaState);
CLuaStackChecker lsc(luaState);
_Table.push();
_Key.push();
_HasNext = false;
if (_NextFunction.callNoThrow(2, 2))
{
_Value.pop(*luaState);
_Key.pop(*luaState);
_HasNext = !_Key.isNil();
_Value.setId(_Table.getId() + "." + _Key.toString());
}
}
}