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 define some or all of the following 3 functions and your module name resolution will be favoured over filesystem queries:
-
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.
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.
Inherited low-numbered file descriptors
After file descriptors are allocated in the process table by some library, it’s
already too late detect whether some arbitrary file descriptor was inherited
from the parent process. If you’re planning to allow Lua-programs to take
control over these file descriptors (i.e. system.get_lowfd()
), you must add
some logic at the program startup (before any new file descriptor is allocated)
to check which file descriptors were inherited and are allowed to be manipulated
from Lua programs and store them in the Emilua registry:
std::array<bool, 7> lowfds;
lowfds.fill(false);
for (int fd = 3 ; fd <= 9 ; ++fd) {
if (fcntl(fd, F_GETFD) != -1 || errno != EBADF) {
lowfds[fd - 3] = true;
}
}
// ...
emilua::app_context appctx;
appctx.lowfds = lowfds;
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);
libemilua-main
If aren’t trying to embed Emilua into an existing application and just want to
create a single-binary application embedding the Lua sources it’ll be easier to
just use libemilua-main. It’s a ready to roll implementation for the function
main()
with some hooks you may use to customize simple behavior.
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.