Often times you may want to represent an object that contains methods and data-members in Lua. We can do this using the SL::Table
, where the methods are functions and the data-members are anything else. SL::Lib::Base
helps us with this process. We can create a C++ library that is passable to Lua as a table like so
{
ExampleLib() : Base("ExampleLib",
{
{ "run", run },
{ "printValue", printValue }
})
{ }
static int run(SL::State state)
{
using SL::CompileTime;
return 1;
}
static int printValue(SL::State state)
{
using SL::CompileTime;
std::cout << obj.get<SL::String>("value") << "\n";
return 0;
}
};
Definition TypeMap.hpp:19
Represents a base library package in a Lua script.
Definition Lib.hpp:19
static std::tuple< Args... > extractArgs(State L)
Helper function extracting a list of types arguments from a Lua stack.
Definition Lib.hpp:66
Then, we can construct a Lua function to consume this object
function ExampleObj(obj)
obj:printValue()
obj.value = obj:run()
obj:printValue()
return obj
end
The :
syntax means we're calling the function stored at printValue
and passing the obj
in as the first argument (a lot like the self
syntax from Python). So, we can call this Lua function from C++ like
ExampleLib lib;
auto table = lib.toTable();
table.set("value", "Hi");
const auto res = runtime.runFunction<
SL::Table>(
"ExampleObj", table);
SL_ASSERT(res, "Error running function: " << res.error().message());
std::cout << std::get<0>(*res).get<SL::String>("value") << "\n";
This prints
Serializing
The primary utility of this struct is that commonly we have structures in C++ that we want to expose to Lua scripts which in turn call back to C++ in order to get values or modify members. This is typically done by writing a library like ExampleLib
above, but adding a void*
member that points to the object you're modifying, or is a int64_t
id that you use in an id system (like an ECS). This kind of work flow could occur as follows.
Firstly, we have our data structure
struct Object
{
struct
{
float x, y;
} position;
float rotation;
};
Then we have a library for this object
{
ObjectLib() : Base("ObjLib",
{
{ "getRadius", getRadius }
})
{ }
static int getRadius(SL::State state)
{
using SL::CompileTime;
const auto* real_obj = static_cast<Object*>(obj.get<void*>("ptr"));
std::sqrtf(powf(real_obj->position.x, 2) + powf(real_obj->position.y, 2));
);
return 1;
}
};
Now we can write a Lua function that takes this object
function GetRadius(obj)
print(obj:getRadius())
end
Then we construct the object and call this Lua function
ObjectLib lib;
auto table = lib.toTable();
Object obj;
obj.position.x = 1,
table.set<void*>("ptr", &obj);
const auto res = runtime.runFunction<
SL::Table>(
"GetRadius", table);
assert(res);
This prints 1
.
- Note
- This may seem counter intuitive, but the purpose is broader. If you have an an ECS, you can construct these tables as functionality associated with an entity ID. Then, the C++ functions can manipulate the actual data, while the logical flow happens in the Lua script. An example of this kind of functionality is this basic movement script
function Start(world, entity)
local rigidbody = entity:getComponent(Component.Rigidbody)
rigidbody.linearDrag = 10.0
entity:setComponent(rigidbody)
end
function Update(world, entity)
local force = {
x = 0, y = 0
}
if Input.getDown("D") then
force.x = force.x + 1
end
if (Input.getDown("A")) then
force.x = force.x - 1
end
if (Input.getDown("S")) then
force.y = force.y + 1
end
if (Input.getDown("W")) then
force.y = force.y - 1
end
local rigidbody = entity:getComponent(Component.Rigidbody)
local magnitude = 4000
force = Math.normalize(force)
force.x = force.x * magnitude
force.y = force.y * magnitude
rigidbody.addedForce.x = force.x;
rigidbody.addedForce.y = force.y;
entity:setComponent(rigidbody)
end