Domain Track
Difficulty 3/5Embedding Scripting Languages
C++ scripting integration — embedding Lua, embedding Python (pybind11/CPython API), sol2 for Lua, exposing C++ APIs to scripts, and hot reloading.
TL;DR
Embedding a scripting language lets users extend your app without recompiling. Lua is the standard for game/embedded use (small, fast, designed to be embedded). Python (via pybind11) is better for scientific/data tooling. Both allow hot-reload of scripts at runtime.
Lua via sol2 (Recommended)
sol2 is a modern C++ wrapper around the Lua C API:
cpp
#include <sol/sol.hpp>
int main() {
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string);
// Execute a script string
lua.script(R"(
function greet(name)
return "Hello, " .. name .. "!"
end
)");
// Call Lua function from C++
std::string result = lua["greet"]("World");
std::println("{}", result); // Hello, World!
// Set/get global variables
lua["player_speed"] = 5.0;
double speed = lua["player_speed"];
// Run a script file
lua.script_file("game_logic.lua");
}Exposing C++ Types to Lua (sol2)
cpp
struct Vec2 { float x, y; };
struct Player {
Vec2 pos{};
std::string name;
int health = 100;
void move(float dx, float dy) { pos.x += dx; pos.y += dy; }
bool is_alive() const { return health > 0; }
};
sol::state lua;
lua.open_libraries(sol::lib::base);
// Register Vec2
lua.new_usertype<Vec2>("Vec2",
sol::constructors<Vec2(), Vec2(float, float)>(),
"x", &Vec2::x,
"y", &Vec2::y
);
// Register Player
lua.new_usertype<Player>("Player",
"name", &Player::name,
"health", &Player::health,
"pos", &Player::pos,
"move", &Player::move,
"is_alive", &Player::is_alive
);
Player p;
p.name = "Hero";
lua["player"] = &p; // expose existing C++ object to Lua
lua.script(R"(
player:move(1.0, 2.0)
print(player.name .. " at " .. player.pos.x .. "," .. player.pos.y)
)");Calling C++ from Lua (Callbacks)
cpp
// Register a C++ function in Lua
lua.set_function("spawn_enemy", [](std::string type, int level) -> int {
std::println("spawning {} at level {}", type, level);
return 42; // entity ID
});
lua.script(R"(
local id = spawn_enemy("goblin", 3)
print("spawned entity " .. id)
)");
// Register a table of functions (namespace-like)
lua["Engine"] = lua.create_table_with(
"log", [](std::string msg) { std::println("[LUA] {}", msg); },
"time", []() -> double { return /* get time */0.0; }
);
lua.script("Engine.log('hello from Lua')");Hot Reloading Scripts
cpp
class ScriptEngine {
sol::state lua_;
std::filesystem::file_time_type last_modified_;
std::filesystem::path script_path_;
public:
void load(std::filesystem::path path) {
script_path_ = path;
reload();
}
void reload() {
try {
lua_.script_file(script_path_.string());
last_modified_ = std::filesystem::last_write_time(script_path_);
std::println("reloaded {}", script_path_.string());
} catch (const sol::error& e) {
std::println(stderr, "script error: {}", e.what());
}
}
void tick() {
// Check if script changed
auto mtime = std::filesystem::last_write_time(script_path_);
if (mtime != last_modified_) reload();
// Call the Lua update function
if (auto fn = lua_["update"]; fn.valid())
fn(/* delta_time */ 0.016);
}
};Python via pybind11
For scientific/ML tooling where Python ecosystem is needed:
cpp
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;
// Expose a C++ function to Python
PYBIND11_MODULE(mymodule, m) {
m.def("add", [](int a, int b) { return a + b; }, "Add two integers");
py::class_<Player>(m, "Player")
.def(py::init<std::string>())
.def_readwrite("health", &Player::health)
.def("move", &Player::move)
.def("is_alive", &Player::is_alive);
}
// Then in Python:
// import mymodule
// p = mymodule.Player("Hero")
// p.move(1.0, 0.0)CMake Setup
cmake
# Lua + sol2
find_package(Lua REQUIRED)
FetchContent_Declare(sol2 GIT_REPOSITORY https://github.com/ThePhD/sol2.git GIT_TAG v3.3.0)
FetchContent_MakeAvailable(sol2)
target_link_libraries(myapp PRIVATE ${LUA_LIBRARIES} sol2)
target_include_directories(myapp PRIVATE ${LUA_INCLUDE_DIR})
# pybind11
find_package(pybind11 CONFIG REQUIRED)
pybind11_add_module(mymodule src/bindings.cpp)
target_link_libraries(mymodule PRIVATE mylib)