Short guide on Luabind¶
The Luabind library is used to export C++ classes to the Lua scripting interface. Luaponte is a (temporary) fork from Luabind.
The typical wrapper pattern is illustrated with code from the Verlet integrator.
Let us start with the declaration of the abstract base class
halmd::mdsim::integrator
. It is qualified for Lua export by the static
method luaopen
.
// from file halmd/mdsim/integrator.hpp
#include <lua.hpp>
template <int dimension>
class integrator
{
public:
static void luaopen(lua_State* L);
integrator() {}
virtual ~integrator() {}
virtual void integrate() = 0;
virtual void finalize() = 0;
virtual double timestep() const = 0;
virtual void timestep(double timestep) = 0;
};
The static method luaopen
defines all entities that should be visible to Lua
by means of Luabind. It is called by the library constructor, i.e., before
programme execution starts.
// from file halmd/mdsim/integrator.cpp
#include <halmd/utility/lua/lua.hpp>
template <int dimension>
void integrator<dimension>::luaopen(lua_State* L)
{
using namespace luabind;
// dimension-dependent class name: integrator_2_, integrator_3_
static std::string class_name("integrator_" + boost::lexical_cast<std::string>(dimension) + "_");
// register a new Lua module
module(L)
[
namespace_("libhalmd")
[
namespace_("mdsim")
[
class_<integrator, boost::shared_ptr<integrator> >(class_name.c_str())
// no constructor, this is an abstract base class
.property("timestep", (double (integrator::*)() const) &integrator::timestep)
.def("integrate", &integrator::integrate)
.def("finalize", &integrator::finalize)
]
]
];
}
HALMD_LUA_API int luaopen_libhalmd_mdsim_integrator()
{
integrator<3>::luaopen(L);
integrator<2>::luaopen(L);
return 0;
}
FIXME explain the template arguments of class_
FIXME explain the differences between property, def, def_readonly, add comments in code sample
FIXME explain HALMD_LUA_API
The actual class for the Verlet module derives from its abstract interface
class. Again, it has a static method luaopen
. Its constructor describes the
dependencies from other modules (particle, box) and specific parameters
(timestep). Further, it is good practice to define a type _Base
pointing at
the base class.
The class contains another static method module_name
, which
is used by FIXME some Lua script to select the integrator via a global
command line option.
// from file halmd/mdsim/host/integrators/verlet.hpp
template <int dimension, typename float_type>
class verlet
: public mdsim::integrator<dimension>
{
public:
typedef mdsim::integrator<dimension> _Base;
typedef host::particle<dimension, float_type> particle_type;
typedef mdsim::box<dimension> box_type;
static char const* module_name() { return "verlet"; }
boost::shared_ptr<particle_type> particle;
boost::shared_ptr<box_type> box;
static void luaopen(lua_State* L);
verlet(
boost::shared_ptr<particle_type> particle
, boost::shared_ptr<box_type> box
, double timestep
);
virtual void integrate();
virtual void finalize();
virtual void timestep(double timestep);
virtual double timestep() const;
};
Export to Lua is similar as for the base class. The main difference is that a
constructor is defined using def
and that a wrapper is needed for the static
method module_name
.
// from file halmd/mdsim/host/integrators/verlet.hpp
template <int dimension, typename float_type>
static char const* module_name_wrapper(verlet<dimension, float_type> const&)
{
return verlet<dimension, float_type>::module_name();
}
template <int dimension, typename float_type>
void verlet<dimension, float_type>::luaopen(lua_State* L)
{
using namespace luabind;
// dimension-dependent class name: verlet_2_, verlet_3_
static string class_name(module_name() + ("_" + lexical_cast<string>(dimension) + "_"));
// register a new Lua module
module(L)
[
namespace_("libhalmd")
[
namespace_("mdsim")
[
namespace_("host")
[
namespace_("integrators")
[
class_<verlet, shared_ptr<_Base>, bases<_Base> >(class_name.c_str())
.def(constructor<
shared_ptr<particle_type>
, shared_ptr<box_type>
, double>()
)
.property("module_name", &module_name_wrapper<dimension, float_type>)
]
]
]
]
];
}
HALMD_LUA_API int luaopen_libhalmd_mdsim_integrators_verlet()
{
verlet<3, double>::luaopen(L);
verlet<2, double>::luaopen(L);
return 0;
}
FIXME explain the three template arguments of class_
FIXME explain why we need a wrapper for module_name
. Is it due to a
deficiency of luabind?
FIXME add some Lua code that exemplifies the usage of the exported module
require("halmd.mdsim.integrator")
integrator = assert(libhalmd.mdsim.host.integrators.verlet_2_)
print(integrator.module_name())
instance = integrator(particle, box, 0.001)
instance:integrate()
instance:finalize()
FIXME when do you use a .
and when a :
for member access? Like core:run()
but integrator.module_name()
?
Lua properties¶
When an object is created from a C++ class registered with Luabind,
Luabind actually creates a C++ object representation object that
wraps this C++ object. This means Luabind C++ objects may be extended
in Lua with arbitrary member functions or variables. One method of
extending a C++ object is with Luabind’s property()
function, which
works analogous to Luabind’s C++ .property()
. Properties may be
read-only or read-write.
In the first example, we create an object from the C++ class
potential_module
, and add a read-only Lua property
potential.name
. This is done by calling property()
with a
function as its first argument, where the function itself receives the
object (self
) and returns the property value ("Lennard Jones"
).
Note how we do not give this getter function a name, but conveniently
define an unnamed function within the property()
call.
local potential = libhalmd.potential_module()
-- set read-only Lua property
potential.name = property(function(self)
return "Lennard Jones"
end)
In the second example, we add a read-write Lua property. We declare a
local variable name
, which is referenced by the local functions
get_name
and set_name
. In C++ language terms, you may consider
name
a private member variable. To add the read-write property, we
pass the getter and setter functions to property()
as first and
second argument, respectively.
-- set read-write Lua property
local name
local function get_name(self)
return name
end
local function set_name(self, value)
name = value
end
potential.name = property(get_name, set_name)
Debugging C++ types with class_info¶
Luabind provides a function class_info
, which queries the class type of a
Lua value. This is especially useful to debug No matching overload found
errors, where the Lua value provided as an argument to a C++ function does not
match the function signature(s).
class_info
returns an object with the properties name
, methods
and attributes
. In this example, we inspect a thermodynamics object:
local thermodynamics = halmd.observables.thermodynamics{}
local c = class_info(thermodynamics)
print(c.name) -- thermodynamics_3_
print(c.methods) -- table: 0x1637390
print(c.attributes) -- table: 0x16373e0
The thermodynamics class only exports a constructor function:
for k, v in pairs(class_info(thermodynamics).methods) do
print(k, v)
end
-- __init function: 0x10fd410
Its object provides signal slots and read-only data slots:
for k, v in pairs(class_info(thermodynamics).attributes) do
print(k, v, class_info(thermodynamics[v]).name)
end
-- 1 en_kin function<double ()>
-- 2 en_tot function<double ()>
-- 3 prepare signal<void ()>::slot_function_type
-- 4 en_pot function<double ()>
-- 5 virial function<double ()>
-- 6 pressure function<double ()>
-- 7 sample signal<void ()>::slot_function_type
-- 8 temp function<double ()>
-- 9 hypervirial function<double ()>
-- 10 v_cm function<fixed_vector<double, 3> ()>