PrevUpHomeNext

API Callback

An "API Callback" is a member function of an API object.

API Callbacks are a convenient way to make functions that appear as "global" free functions in lua, but whose implementation in C++ has lots of useful things in scope.

They enjoy dispatch to the API object using the "lua_extraspace" method, which is the fastest one. And they are adapted using PRIMER_ADAPT so that they can easily take complex function parameters.

There are two steps to using API Callbacks.

API feature

The API feature that does this is primer::api::callbacks.

struct my_api : primer::api::base<my_api> {

  API_CALLBACK(foo, ("This is the foo help")) (lua_State * L, std::string arg) -> primer::result {

  }

  API_FEATURE(primer::api::callbacks, callbacks_);

  ...

  my_api()
   : L_(luaL_newstate())
   , callbacks_(this)
  {}
};

Most of the standard API features that we provide, like userdatas and libraries, don't require special initialization, they can simply be default initialized, i.e. left out of the intializer list in a ctor of the API object.

This is not so of the callbacks object. It needs a pointer to the api object, this. It remembers this pointer, so that it can later put it in the lua extraspace when initialize_api is called.

Another thing happens when it is initialized with this: It looks at the type of the pointer it recieved, and fetches from that the callbacks_array(). This is where it obtains the list of functions that it should manage.

Registration of callbacks

The actual callbacks array is assembled by the api::base itself, based on declarations that it sees in the api object body.

All callbacks are registered via a macro USE_API_CALLBACK.

This macro takes two parameters: * a token which becomes the name of the function in lua * a function pointer, which is adapted using the macro PRIMER_ADAPT_EXTRASPACE

Thus, this macro can be used with free functions, as well as member functions -- it will just fallback to PRIMER_ADAPT in that case.

The macro which we saw in examples was simply API_CALLBACK. That macro is defined as follows:

#define NEW_LUA_CALLBACK_1(name)                                               \
  USE_LUA_CALLBACK_2(name, &owner_type::intf_##name);                          \
  auto intf_##name

#define NEW_LUA_CALLBACK_2(name, help)                                         \
  USE_LUA_CALLBACK_3(name, help, &owner_type::intf_##name);                    \
  auto intf_##name

#define NEW_LUA_CALLBACK(...) PRIMER_PP_OVERLOAD(NEW_LUA_CALLBACK_, __VA_ARGS__)

In this definition, owner_type is a typedef created by api::base. The macro simply calls USE_API_CALLBACK using a member function intf_##name, yet to be created. Then it opens a definition for that member function, using auto for trailing return specifier.

So, there is no magic here. USE_API_CALLBACK is what is doing the work, and all it needs is a function pointer.

If you prefer the syntax, you can just declare all your callbacks "normally" and without macros, and then register them one-by-one using USE_API_CALLBACK. (But, it creates another point of maintanence failure since you have to manually update this list when functions are added or removed. You won't get a compiler error if you define a new callback function and forget to register it.)

TODO example


PrevUpHomeNext