Binding new type
Adding new type to the doc generator without binding internals
If a C++ type has not been registered in the doc generator, it will show up as
<cppval: **gibberish** >
. To mitigate this problem, you can add
LUNA_VAL( your_type, "YourType" )
in catalua_luna_doc.h
, and the generator will use YourType
string for argument type.
Binding new type to Lua
First, we need to register the new type with the bindings system. It needs to be done for many
reasons, including so that the doc generator understands it, and the runtime can deserialize from
JSON any Lua table that contains that type. If you don't you'll get a compile error saying
Type must implement luna_traits<T>
.
-
In
catala_luna_doc.h
, add declaration for your type. For example, if we're binding an imaginaryhorde
type (which is astruct
), it will be a single line near the top of the file:struct horde;
Complex templated types may need to actually pull in the relevant header, but please avoid it as it heavily impacts compilation times.
-
In the same file, register your type with the doc generator. Continuing with the
horde
example, it's done like this:LUNA_VAL( horde, "Horde" );
While C++ types use all kinds of style for their names, on Lua side they all should be in
CamelCase
.
Now we can actually get to the details. The bindings are implemented in catalua_bindings*.cpp
files. They are spread out into multiple .cpp
files to speed up compilation and make it easy to
navigate, so you can put yours into any existing catalua_bindings*.cpp
file or make your own
similar file. They are also spread out into functions, for the same reasons. Let's register our
horde
type, and put it in a new file and a new function:
- Add a new function declaration in
catalua_bindings.h
:void reg_horde( sol::state &lua );
- Call the function in
reg_all_bindings
incatalua_bindings.cpp
:reg_horde( lua );
- Make a new file,
catalua_bindings_horde.cpp
, with the following contents:#include "catalua_bindings.h" #include "horde.h" // Replace with the header where your type is defined void cata::detail::reg_horde( sol::state &lua ) { sol::usertype<horde> ut = luna::new_usertype<horde>( lua, luna::no_bases, luna::constructors < // Define your actual constructors here horde(), horde( const point & ), horde( int, int ) > () ); // Register all needed members luna::set( ut, "pos", &horde::pos ); luna::set( ut, "size", &horde::size ); // Register all needed methods luna::set_fx( ut, "move_to", &horde::move_to ); luna::set_fx( ut, "update", &horde::update ); luna::set_fx( ut, "get_printable_name", &horde::get_printable_name ); // Add (de-)serialization functions so we can carry // our horde over the save/load boundary reg_serde_functions( ut ); // Add more stuff like arithmetic operators, to_string operator, etc. }
- That's it. Your type is now visible in Lua under name
Horde
, and you can use the binded methods and members.
Binding new type to Lua (using Neovim's regex)
Binding classes/structs to Lua by hand can be quite tedious, which is why another way to bind a class is by transforming its header file. For the third step of the second part from previously, it's possible to use Neovim's built-in regex and C++ macros to bind the class for us.
- Make a copy of the class definition.
- Apply both:
%s@class \([^{]\)\+\n*{@private:@
%s@struct \([^{]\)\+\n*{@public:@
- Manually remove the constructors/unwanted methods at the beginning.
- Delete all
private
/protected
methods:%s@\(private:\|protected:\)\_.\{-}\(public:\|};\)@\2
- Remove
};
at the end of the class definition. - Delete
public
labels:%s@ *public:\n@
- Delete comments:
%s@\( *\/\*\_.\{-}\*\/\n\{,1\}\)\|\( *\/\/\_.\{-}\(\n\)\)@\3@g
- Unindent until there is zero base indentation.
- Turn method definitions into declarations:
%s@ *{\(}\|\_.\{-}\n^}\)@;
- Push most method declarations into a single line:
%s@\((\|,\)\n *@\1@g
- Remove default values:
%s@ *= *\_.\{-}\( )\|;\|,\)@\1@g
- Remove
overriden
/static
methods/members andusing
s:%s@.*\(override\|static\|using\).*\n@@g
- Remove
template
s:%s@^template<.*>\n.*\n@@g
- Remove
virtual
tag:%s@^virtual *@
- Check if all lines end in a semicolon:
%s@\([^;]\)\n@\0@gn
- Count how many functions there are:
%s@\(.*(.*).*\)@@nc
- Push first found function to the end:
%s@\(.*(.*).*\)\n\(\n*\)\(\_.*\)@\3\1\2
- Now you'll want to repeat step 16 for the number of matches in step 15 minus one. For Neovim, input the match count minus one, '@', then ':', e.g. '217@:' repeats the last command 217 times.
- Clean up new lines:
%s@\n\{3,}@\r\r
- Wrap methods into a macro:
%s@\(.*\) \+\([^ ]\+\)\((.*\);@SET_FX_T( \2, \1\3 );
- Wrap members into a macro; make sure to select which lines to affect first:
s@.\{-}\([^ ]\+\);@SET_MEMB( \1 );
- Make the previously multi-line method declarations span multiple lines again:
%s@\(,\)\([^ ]\)@\1\r \2@g
Now what's left to do is to take the chunk of text and use it in a Lua binding. Continuing with the horde example, this is how the code should look like with these macros:
#include "catalua_bindings.h"
#include "catalua_bindings_utils.h"
#include "horde.h" // Replace with the header where your type is defined
void cata::detail::reg_horde( sol::state &lua )
{
#define UT_TYPE horde
sol::usertype<UT_TYPE> ut =
luna::new_usertype<UT_TYPE>(
lua,
luna::no_bases,
luna::constructors <
// Define your actual constructors here
UT_TYPE(),
UT_TYPE( const point & ),
UT_TYPE( int, int )
> ()
);
// Register all needed members
SET_MEMB( pos );
SET_MEMB( size );
// Register all needed methods
SET_FX_T( move_to, ... ); // Instead of ..., there'd be the type declaration of the method.
SET_FX_T( update, ... );
SET_FX_T( get_printable_name, ... );
// Add (de-)serialization functions so we can carry
// our horde over the save/load boundary
reg_serde_functions( ut );
// Add more stuff like arithmetic operators, to_string operator, etc.
// ...
#undef UT_TYPE // #define UT_TYPE horde
}
This method of binding to Lua lacks template method bindings and may be broken: compiler errors, linker freezes, so it's best to assume these bindings will be broken by default, only needing slight fixes / manual additions.
Binding new enum to Lua
Binding enums is similar to binding types. Let's bind an imaginary horde_type
enum here:
- If enum does not have an explicitly defined container (the
: type
part afterenum name
in the header where it's defined), you'll have to specify the container first, for example:// hordes.h - enum class horde_type { + enum class horde_type : int { animals, robots, zombies }
- Add the declaration to
catalua_luna_doc.h
enum horde_type : int;
- Register it in
catalua_luna_doc.h
withLUNA_ENUM( horde_type, "HordeType" )
- Ensure the enum implements the automatic conversion to/from
std::string
, seeenum_conversions.h
for details. Some enums will already have it, but most won't. Usually it's just a matter of specializingenum_traits<T>
for your enumT
in the header, then definingio::enum_to_string<T>
in the.cpp
file with enum -> string conversion. Some enums won't have the "last" value required forenum_traits<T>
. In that case, you'd have to add one:
Note that this only works for "monotonic" enums, i.e. ones that start with 0 and don't skip any values. In the example above,enum class horde_type : int { animals, robots, - zombies + zombies, + num_horde_types }
animals
has implicit value of0
, robots has implicit value of1
andzombies
has implicit value of2
, so we can easily addnum_horde_types
, which will have correct and expected implicit value of3
. - Bind enum fields in
reg_enums
function incatalua_bindings.cpp
:
This uses the automatic convertion from step 4, so we have equal names between JSON and Lua.reg_enum<horde_type>( lua );
Binding new string_id<T>
or int_id<T>
to Lua
Binding these can be done separately from binding T
itself.
- Register your type
T
with the doc generator if you haven't already (see relevant docs). - Replace
LUNA_VAL
from step 1 withLUNA_ID
. - Ensure your type
T
implements operators<
and==
. It's usually easy implement them manually, and can be done semi-automatically with macroLUA_TYPE_OPS
found incatalua_type_operators.h
. - Ensure your type
T
has a nullstring_id
. You can add one if it doesn't exist instring_id_null_ids.cpp
. Use theMAKE_CLASS_NULL_ID
macro ifT
is defined as a class,MAKE_STRUCT_NULL_ID
macro otherwise. - Ensure your type's
T
string_id
hasobj()
andis_valid()
methods implemented. These methods are implemented on a case-by-case basis. Checking otherstring_id
s as example is recommended. - In
catalua_bindings_ids.cpp
, add the header where your type T is defined:#include "your_type_definition.h"
- In
reg_game_ids
function, register it like so:reg_id<T, true>( lua );
That true
can be replaced with false
if you only want to bind string_id<T>
and don't care
about (or can't implement) int_id<T>
.
You may get linker errors at this stage, e.g. about is_valid()
or NULL_ID()
methods, which are
for various reasons not implemented forall string or int ids. In this case, you'll have to define
these manually, see relevant docs on string_id
and int_id
for more info.
And that's it. Now, your type T
will show up in Lua with Raw
postfix, string_id<T>
will have
Id
postfix, and int_id<T>
will have IntId
postfix. As example, for
LUNA_ID( horde, "Horde" )
, we'll get:
horde
->HordeRaw
string_id<horde>
->HordeId
int_id<horde>
->HordeIntId
All type conversions between the 3 are implemented automatically by the system. Actual fields and methods ofT
can be binded to Lua same way as usual.