C++ embedder API
If you want to embed Emilua in your own Boost.Asio-based programs, this is the list of steps you need to do:
-
Compile and link against Emilua (use Meson or pkg-config to have the appropriate compiler flags filled automatically).
-
#include <emilua/state.hpp>
-
Instantiate
emilua::app_context
. This object needs to stay alive for as long as at least one Lua VM is alive. If you want to be sure, just make sure it outlivesboost::asio::io_context
and you’re good to go. -
Create an
emilua::properties_service
object with the same concurrency hint passed toboost::asio::io_context
and add it to theboost::asio::io_context
object usingboost::asio::make_service
. This step will be needed for as long as Boost.Asio refuses to add a getter for the concurrency hint: https://github.com/chriskohlhoff/asio/pull/1254. -
Call
emilua::make_vm()
(seesrc/main.ypp
for an example). -
Call
emilua::vm_context::fiber_resume()
inside the strand returned byemilua::vm_context::strand()
to start the Lua VM created in the previous step (seesrc/main.ypp
for an example). -
Optionally synchronize against other threads before you exit the application. If you’re going to spawn actors in foreign
boost::asio::io_context
objects in your Lua programs then it’s a good idea to include this step. See below.
Emilua is not designed to work properly with
boost::asio::io_context::stop() . Many cleanup steps will be missed if you call
this function. If you need to use it, then spawn Emilua programs in their own
boost::asio::io_context instances.
|
emilua::app_context
This type stores process-wide info that is shared among all Lua VMs (e.g. process arguments, environment, module paths, module caches, default logger, which VM is the master VM, …).
If you want to embed the Lua programs in your binary as well you can
pre-populate the module cache here with the contents of all Lua files you intend
to ship in the binary. modules_cache_registry
is the member you’re looking
for. Do this before you start the first Lua VM. However there’s a better way
(see next section).
Builtin modules
Just use linker args to override some or all of the following 3 functions:
-
emilua_get_builtin_module
-
emilua_get_builtin_rdf_ec
-
emilua_get_builtin_native_module
Using this method instead of pre-filling the module cache allows actors spawned in subprocesses to import these modules as well. This method may also lead to better start-up times and a smaller memory footprint (you could even use gperf to have the best module search performance).
Master VM
If you want to allow your Lua programs to change process state that is shared among all program threads (e.g. current working directory, signal handlers, …) then you need to elect one Lua VM to be the master VM.
The 1-one snippet that follows is enough to finish this setup. This step must be
done before you call fiber_resume()
.
appctx.master_vm = vm_ctx;
Cleanup at exit
First make sure emilua::app_context
outlives boost::asio::io_context
.
After boost::asio::io_context::run()
returns you can use the following snippet
to synchronize against extra threads and boost::asio::io_context
objects your
Lua scripts created[1].
{
std::unique_lock<std::mutex> lk{appctx.extra_threads_count_mtx};
while (appctx.extra_threads_count > 0)
appctx.extra_threads_count_empty_cond.wait(lk);
}
Actors spawned in different processes
Emilua has the ability to spawn Lua VMs in their own processes for isolation or sandboxing purposes. To enable this feature, a supervisor process must be created while the program is still single-threaded.
For communication with the supervisor process, Emilua uses an UNIX socket. The
file descriptor for this process is stored in
app_context::ipc_actor_service_sockfd
. See src/main.ypp
for an example on
how to initialize this variable.
You also need to initialize emilua::app_context::environp
. This is a pointer
to environ
. This step must be done before you fork to create the supervisor
process. Emilua uses an indirection instead of using environ
directly because
FreeBSD is not POSIX-compliant and the usual declaration for environ
doesn’t
work from shared libraries. Emilua never modifies the environment for the main
process, so you don’t need to worry about what Emilua uses this variable
for. Its usage is internal to Emilua and won’t affect your C++ program.
#if BOOST_OS_UNIX
emilua::app_context::environp = &environ;
#endif // BOOST_OS_UNIX
On Linux, you also need to initialize emilua::clone_stack_address
.
If you don’t intend to have Lua VMs tied to their own processes triggered by Lua programs then you can skip this step.
RT signals
Emilua reserves a RT signal for internal uses (cancelling IO operations which have poor system APIs). This signal can be configured at build time:
meson configure -Deintr_rtsigno=RTSIGNO
If you choose the value 0
, this support is disabled altogether and Emilua
won’t reserve any RT signal by itself. If this support is enabled, you must add
some code similar to the following one in main()
:
struct sigaction sa;
std::memset(&sa, 0, sizeof(struct sigaction));
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, EMILUA_CONFIG_EINTR_RTSIGNO);
sigprocmask(SIG_BLOCK, &sa.sa_mask, /*oldset=*/NULL);
sa.sa_sigaction = emilua::longjmp_on_rtsigno;
sa.sa_flags = SA_RESTART | SA_SIGINFO;
sigaction(EMILUA_CONFIG_EINTR_RTSIGNO, /*act=*/&sa, /*oldact=*/NULL);
boost::asio::io_context
objects if your Lua programs explicitly ask for that when it calls spawn_vm()
. You can also disable this feature altogether at build time.