Skip to content
Romain Milbert edited this page May 28, 2024 · 22 revisions

A Lua wrapper is available to make use of the Lua language as a scripting language for the engine. This wrapper uses Sol2, a library allowing to bind C++ features in Lua (and conversely) extremely easily.

There is nothing to do to use the engine with Lua: just build it or get the artifacts, and you will be good to go. If you do not need the wrapper though, you can avoid compiling it by giving -DRAZ_USE_LUA=OFF when generating the project with CMake.

All important functionalities are normally available using Lua. If some are not, please file an issue or create a pull request to add them! To see some examples, check the repo's scripts.

Lua application

The engine can be used entirely with Lua; see the script demo and the related script to get you started. You can run the demo giving it any Lua script; simply drag & drop a .lua file onto the executable, or run it with ./scriptDemo path/to/script.lua.

Lua script component

Any entity can also have a script component, which needs a specific format to be valid:

-- Constants and/or global variables can be defined here

function setup()
    -- Code here will be executed once whenever the script is (re)loaded; this function is entirely optional
    -- You can use the special 'this' variable, which represents the entity containing the script
    -- For example: this:getTransform().position = Vec3f.new(0, 0, 0)
end

function update(timeInfo)
    -- Code here will be executed every frame; this function is required to exist
    -- The 'timeInfo' parameter contains the same fields that are given to each system & world (deltaTime, globalTime, ...)
    -- For example: this:getTransform():translate(0, math.sin(timeInfo.globalTime) * timeInfo.deltaTime, 0)
end

To add a script to an entity, simply do

Raz::LuaScript& luaScript = entity.addComponent<Raz::LuaScript>(/* Lua code */);

You can also reload the script by using luaScript.loadCode(...) given some code, or luaScript.loadCodeFromFile(...) given a file path.

Finally, you can register any other entity besides the default this to be used from within the script:

Raz::Entity& mesh = ...;
// The given string will be the name the entity is available from within the script
luaScript.registerEntity(mesh, "myMesh");
-- No setup() function is needed in our case

function update(timeInfo)
    -- Always stay one unit above the 'myMesh' entity
    this:getTransform().position = myMesh:getTransform().position + Vec3f.new(0, 1, 0)
end

Specificities

Functionalities that differ from their C++ counterpart are listed below.

General

  • As Lua does not support templates, rvalues and explicit move, each System can be added in a World through its specific function, like world:addRenderSystem(...), world:addPhysicsSystem(...), etc. These are meant to be strictly equivalent to the world.addSystem<YourSystem>(...) you would do in C++, meaning they take the same arguments in both languages. Likewise for adding RenderProcesses in a RenderGraph: use renderGraph:addBloomRenderProcess(...), renderGraph:addConvolutionRenderProcess(...), etc.
  • Unlike the above, components can be added using overloads of Entity::addComponent(): simply use entity:addComponent(Transform.new(...)), entity:addComponent(Mesh.new(...)), etc. Components being meant to be (almost exclusively) just data, they can technically be moved or copied around without issue.
  • Object attributes that have both getters & setters, like Transform::get/setPosition(), are accessible on both read & write usages as properties in Lua as transform.position, instead of transform:get/setPosition() as it would normally have been. This may be useful when making heavy use of those, but can be unintuitive and thus might disappear in the future.

Audio

  • The Microphone's recoverData() overload taking a vector is not available, as although it would be possible to make it usable, it would not result in any gain concerning memory allocations. Use the overload returning the vector instead.

Data

  • The templated functions Image::recoverPixel() and Image::setPixel() are bound to recoverByte/FloatPixel()/setByte/FloatPixel() for single channel images & recoverVecNX()/setVecNX() for multi-channel images (N being 2, 3 or 4, and X being b or f; for example, recoverVec2b() for a 2 channels byte image, or setVec4fPixel() for a 4 channels float image).

Math

  • Transform::get/setScale() is accessible with transform.scaling, not scale. This is to avoid a name conflict with the function scale().
  • Vector::lerp() is templated on the C++ side to allow calculations being done in the given type, avoiding truncation if needed. To that end, lerp<float>() is available in Lua as lerpf().

Rendering

  • The ShaderProgram::setAttribute() & UniformBuffer::sendData() overloads taking an int, an unsigned int or a float must be used respectively with setIntAttribute()/setUintAttribute()/setFloatAttribute() & sendIntData()/sendUintData()/sendFloatData(). Lua has no unsigned integers support, and although overloading int and float works fine, it would not be possible to have the unsigned int one; three different functions for each have thus been made. The mathematical vectors & matrices overloads are still usable with the generic setAttribute()/sendData() functions.
    • Specifically for ShaderProgram::setAttribute(), the std::vector<int/unsigned int/float> overloads are not available at all, at least for now, since I do not know if std::vector can be bound easily through Sol.
  • The Texture3D constructors taking image slices are not available; use the load() member function afterward instead.
    • Texture3D::create() does however work with those.

Potential issues

Due to certain mechanisms of Lua, which forces to drop some safety guarantees, care must be taken in order to use the engine properly.

Adding components

As move semantics are not available in Lua, everything that should be (and is) moved on the C++ side, as are most components, are NOT moved explicitly in Lua. For example, to add a Mesh component, you would do:

// In C++

// Constructing in-place...
entity.addComponent<Mesh>(...);

// ... or moving a variable
Raz::Mesh mesh(...);
entity.addComponent<Mesh>(std::move(mesh)); // Moving it explicitly
-- In Lua

-- There is no in-place construction, an object must be given
-- This example works fine, as the component is moved internally and is inaccessible after the call
entity:addComponent(Mesh.new(...))

-- When giving a variable however...
local mesh = Mesh.new(...)
entity:addComponent(mesh) -- No explicit move, but the variable has been!

Due to this, all objects not given directly to the function must NOT be used anymore; adding a component to an entity ALWAYS moves the given object, if applicable. This is obviously the same that is done on the C++ side, but in a hidden way. There is currently not much that can be done to make this error-prone behavior clearer. In the future, a kind of handle mechanism could be used in the Lua wrapper to track lifetimes specifically for this purpose.

Clone this wiki locally