A primer::lua_ref is a reference to a lua value
which exists in a lua VM.
More precisely, it is an RAII object which manages a lua
reference created by luaL_ref
and cleaned up by luaL_unref
(see documentation).
primer::lua_ref doesn't have the semantics of
a C++ reference. It has an empty state, and can be assigned and reassigned
to point to different objects. Effectively, it's more like a smart pointer
which points to some lua value.
To bind a lua_ref to a
lua value,
lua_ref
from the pointer lua_State
* for that stack. This pops
the object from the stack.
If the stack is empty, then the lua_ref
is placed in the empty state.
It can be default constructed in the empty state as well.
The value can be obtained from the reference by using the push methods, or the as
method. The as method will
first push the value to the stack, then try to primer::read
it, then clean up the stack.
If the lua VM is destroyed (closed), the lua_ref
reverts to the empty state.
lua_State * L = luaL_newstate(); std::string foo{"foo"}; primer::push(L, foo); assert(lua_gettop(L) == 1); primer::lua_ref ref{L}; assert(lua_gettop(L) == 0); assert(ref); assert(ref.as<std::string>() && foo == *ref.as<std::string>()); assert(!ref.as<int>()); assert(!ref.as<bool>()); assert(ref.as<primer::truthy>() && true == ref.as<primer::truthy>()->value); assert(ref.push()); assert(lua_gettop(L) == 1); assert(lua_isstring(L, 1)); assert(foo == lua_tostring(L, 1)); lua_pop(L, 1); assert(ref); assert(ref.as<std::string>() && foo == *ref.as<std::string>()); lua_close(L); assert(!ref); assert(!ref.as<std::string>());
There are two components to the lua_ref
object:
int which is the index
returned by luaL_ref.
lua_State.
This lets the lua_ref
determine if the state has been closed. This is basically an implementation
detail.
class lua_ref {lua_state_ref sref_;
mutable int iref_ = LUA_NOREF; public: // Special member functions lua_ref() noexcept = default; lua_ref(lua_ref && other) noexcept;
lua_ref(const lua_ref & other); ~lua_ref() noexcept; lua_ref & operator=(const lua_ref & other); lua_ref & operator=(lua_ref && other) noexcept; // Primary constructor
explicit lua_ref(lua_State * L); // Reset to empty state
void reset() noexcept; // Standard swap function void swap(lua_ref & other) noexcept; // Attempt to obtain a pointer to the underlying lua VM. // Succeeds if it wasn't destroyed yet. // Always returns a pointer to the *main thread* stack.
lua_State * lock() const noexcept; // Push to the main stack // Return value is same as lock()
lua_State * push() const noexcept; // Push to a thread stack (see "coroutines" in the manual)
bool push(lua_State * T) const noexcept; // test validity
explicit operator bool() const noexcept; // Try to interpret the value as a specific C++ type
template <typename T> expected<T> as() const noexcept; };
A weak reference to a lua state. See |
||||
Holds the registry index to the object. Mutable because, if |
||||
If lua can't allocate memory for the copy, throws |
||||
|
Pops an object from the top of given stack, and binds to it. If no object is on top, enters the empty state. Note: This can cause a lua memory allocation failure. |
||||
Releases the lua reference, reverts to empty state. |
||||
Returns a valid lua_State * if successfully locked. Nullptr if not. No-fail. |
||||
|
Note: Does not check for stack space This cannot cause lua memory allocation error. |
||||
|
Attempts to push the object onto the top of a given thread stack. It must be a thread in the same VM as the original stack, or the same as the original stack.
Returns
Returns
|
||||
Test if we can still be locked. |
||||
|
Attempt to cast the lua value to a C++ value, using primer::read This cannot cause an exception or raise a lua error. It performs a stack space check. |
primer::lua_ref object corresponds to the
lua reference created by luaL_ref
somewhere in the registry. lua guarantees that the referenced object
won't be garbage collected as long as this reference exists. So, if
the lua_ref is engaged
and the VM still exists, locking the state and pushing the object are
both noexcept, no-fail
operations. They can't cause lua memory allocation failure either.
primer::lua_ref cannot
take ownership of the lua VM. If lua_close
is called, the object will be collected, and attempts to lock the
lua_ref after that
will yield nullptr. The
operator bool
test will also yield false. Effectively, once the VM is closed, any
outstanding lua_ref
objects revert to the empty state.
lua_ref to
an object on the stack can cause a lua memory
allocation failure, but not std::bad_alloc.
primer::lua_ref is safe in the sense of not
becoming a dangling pointer in any scenario. However, it is not thread-safe,
you should not pass this across threads.
![]() |
Caution |
|---|---|
|
Copying a
When running without
In low memory scenarios, this should likely be avoided. For instance,
a In general prefer to move these whenever possible. |
![]() |
Caution |
|---|---|
You must not pass these objects across operating-system threads. Lua is generally not thread-safe anyways, so this should come as no surprise. |
Using primer::push with a primer::lua_ref
calls the thread-push member function, and so comes with all the caveats
above -- if you push it to an unrelated lua VM, it won't work and bad things
will happen.
Using primer::read with a primer::lua_ref
always succeeds in binding to a value
at the given position, unless it is nil or out of bounds, in which case
it produces a lua_ref in
the empty state.
This makes it effectively an "any" type for purposes of function
parameters, and you can use lua_ref::as
to easily try to interpret it as other values later. So it can be used
sort of as a poor man's variant.
Here's an example function to illustrate:
primer::result ref_test_func(lua_State * L, primer::lua_ref ref) { if (!ref) { lua_pushstring(L, "nil"); return 1; } else if (ref.as<bool>()) { lua_pushstring(L, "bool"); return 1; } else if (ref.as<int>()) { lua_pushstring(L, "int"); return 1; } else if (ref.as<std::string>()) { primer::push(L, ref); return 1; } return primer::error("I can't handle it!"); }
And example semantics:
lua_CFunction f = PRIMER_ADAPT(&ref_test_func); lua_pushcfunction(L, f); lua_setglobal(L, "ref_test_func"); const char * script = "" "assert(ref_test_func() == 'nil') \n" "assert(ref_test_func(5) == 'int') \n" "assert(ref_test_func('foo') == 'foo') \n" "assert(ref_test_func(true) == 'bool') \n" "assert(not pcall(ref_test_func, 5.5)) \n" "assert(not pcall(ref_test_func, {})) \n" "assert(pcall(ref_test_func, nil)) \n"; assert(LUA_OK == luaL_loadstring(L, script)); assert(LUA_OK == lua_pcall(L, 0, 0, 0));
Note that, it is not too difficult to implement your own read and push
specializations for a proper variant type that appears in your program.
We don't currently have a generic boost::variant
header in Primer, so you would have to make your own.