-
Notifications
You must be signed in to change notification settings - Fork 223
Sol Lua Binding Upgrade
If you're here, you're probably aware of how we use Lua (Portuguese for Moon, not an acronym) as a scripting language for quick iteration, and to lower the barrier to entry for newcomers. Aside from a couple of quirks that get under peoples' skin (1-indexed arrays); it is a wonderful language that is very fast to pick up and has surprising versatility.
In order to use Lua alongside our Core C++ code, we need to bind
C++ functions to Lua, and represent C++ Types
as Lua Usertypes
. This is achieved by using a binding system
, or binding library
.
Our current binding system is called Lunar and was originally published as a code snippet around 2009. At the time, there probably wasn't much available in terms of binding libraries, so this would have been a godsend in terms of "easy" interop with C++. Unfortunately, Lunar hasn't been upgraded or iterated on... ever. We haven't touched it (apart from formatting passes) since Sep 16, 2011.
If it was rock-solid and easy to use/understand/extend, then we would be perfectly fine to leave it alone forever. This however is not the case. The binding system and the process of getting information into and out of the Lua state is fragile and arcane. Much developer time has been wasted "fiddling with the stack". Since our adoption of Lunar pre-2011, the Lua-binding ecosystem has moved on and there are a wealth of viable libraries allowing for safer usage, modern workflows, and easier to understand/maintain code.
The most promising and well supported library at the moment is sol (sol2 v3, referred to as Sol/sol for clarity).
#define SOL_ALL_SAFETIES_ON 1
#include "sol/sol.hpp"
int main()
{
sol::state lua(); // Create lua state
lua.open_libraries(); // Open all lua standard libs
lua.script("print('Hello from Lua!')");
return 0;
}
int main()
{
sol::state lua(); // Create lua state
lua.open_libraries(); // Open all lua standard libs
lua.script("function callMe(str) print(str) end");
lua["callMe"]("Test String");
return 0;
}
int main()
{
sol::state lua(); // Create lua state
lua.open_libraries(); // Open all lua standard libs
auto nativeFunc = [](std::string str) { std::cout << str << "\n"; };
lua["nativeFunc"] = nativeFunc;
lua.script("nativeFunc('Hello from nativeFunc!')");
return 0;
}
namespace luautils
{
void sayHello()
{
std::cout << "Hello!\n";
}
}
int main()
{
sol::state lua(); // Create lua state
lua.open_libraries(); // Open all lua standard libs
lua.set_function("sayHello", &luautils::sayHello);
lua.script("sayHello()");
return 0;
}
struct Entity
{
int ID;
int getID()
{
return ID;
}
};
int main()
{
sol::state lua(); // Create lua state
lua.open_libraries(); // Open all lua standard libs
lua.new_usertype<Entity>("Entity",
"getID()", &Entity::getID()
);
lua.script("function printEntityID(entity) print(entity:getID()) end");
auto entity = Entity{42};
lua["printEntityID"](entity);
return 0;
}
// Underlying Entity
struct Entity
{
int ID;
int Zone;
int getID()
{
return ID;
}
int getZone()
{
return Zone;
}
};
// "Wrapper" around underlying Entity
struct LuaEntity
{
Entity* m_PEntity;
int getID()
{
return m_PEntity->ID;
}
int getZone()
{
return m_PEntity->Zone;
}
int getLongID()
{
// Other logic can live here <--
return otherutils::makeLongID(m_PEntity->ID, m_PEntity->Zone);
}
};
int main()
{
sol::state lua(); // Create lua state
lua.open_libraries(); // Open all lua standard libs
lua.new_usertype<LuaEntity>("LuaEntity",
"getID()", &LuaEntity::getID(),
"getZone()", &LuaEntity::getZone(),
"getLongID()", &LuaEntity::getLongID()
);
lua.script("function printEntityInfo(entity) print(entity:getID()); print(entity:getZone()); print(entity:getLongID()); end");
auto entity = Entity{42, 99};
auto luaEntity = LuaEntity{entity}
lua["printEntityInfo"](luaEntity);
return 0;
}
uint32 someExistingBinding(lua_State* L)
{
sol::state_view lua(L); // Create non-owning view into existing lua state
... // Continue using 'lua' and the sol functionality...
return 0;
}
template <typename... Args>
auto safeCall(sol::state& lua, std::string funcName, Args&&... args)
{
auto result = lua[funcName](std::forward<Args>(args)...);
if (!result.valid())
{
sol::error e = result;
printf("lua err: %s\n", e.what());
}
return result;
}
int main()
{
sol::state lua;
lua.open_libraries();
lua.script(R"(
function test0()
print(42)
end
function test1(num1)
print(num1 * num1)
end
function test2(num1, num2)
print(num1 * num2)
end
)");
safeCall(lua, "test0");
safeCall(lua, "test1", 2);
if (!safeCall(lua, "test2", "bad args").valid())
{
printf("Bad times :(");
}
// Prints:
// lua err: [string "..."]:11: attempt to mul a 'string' with a 'nil'
// stack traceback:
// [C]: in metamethod 'mul'
// [string "..."]:11: in function 'test2'
// Bad times :(
return 0;
}
- General
- Client Setup
- Server Setup + Maintenance
- Server Administration
- Development
- Project Meta
- Server List
- Resources