PrevUpHome

Singletons

Sometimes, when implementing an API, it's convenient to create an object which is a "singleton" with respect to the lua State, and to be able to easily refer to this object. Usually you want to do this by creating a unique key for this object inside the lua registry.

When the object is a lua value, primer provides a simple way to do this using primer::push_singleton. When the object is a C++ object external to lua, you can use primer::registry_helper.

Lua value

It is fairly common in the lua API to want to create some private object hidden in the registry to support some feature or hold some implementation detail.

Often times, people make up string constants to use as the registry key, and create a "lazy construction" function which first checks the registry for a pre-cached value, and otherwise computes it.

Primer gives a very terse and convenient way to achieve this:

namespace primer {

template <void (*)(lua_State *)>
void push_singleton(lua_State *);

template <int (*)(lua_State *)>
void push_singleton(lua_State *);

} // end namespace primer

The idea is, the function which creates the value is the template parameter, and the template function ensures the lazy construction aspect.

The registry key is simply the function pointer corresponding to the producer function itself.

Here's an example producer function

void my_table(lua_State * L) {
  lua_newtable(L);
  lua_pushinteger(L, 5);
  lua_setfield(L, -2, "a");
  lua_pushinteger(L, 7);
  lua_setfield(L, -2, "b");
}

The function simply pushes a table of some kind onto the stack. Now, the call

primer::push_singleton<&my_table>(L)

will push our table onto the stack, lazily constructing it if necessary.

C++ value

It sometimes happens that we have an object which we know will outlive lua, and we would like to store a pointer to it in the registry and recover it easily. Especially, this happens when the object is a member of an API object.

We can store and retrieve such objects in the following way:

void primer::registry_helper<T>::store_self(L, T *);
T * primer::registry_helper<T>::obtain_self(L);

Clearly, this only works if this is the unique object of this type which will be registered.

Note that obtain_self has UB if no object of this type was previously stored. If PRIMER_DEBUG is defined, then it will cause an assertion failure.

A different approach is to simply make the object a feature of the API, or just a member variable of the API object. Then it will be in scope of any callback function that you are defining, or can be accessed by any features that need to access it. The registry_helper technique should probably only be used when that's not suitable for some reason.


PrevUpHome