LumixEngine

Meta

Meta scans the source code for lines containing //@ and generates artifacts such as reflection data and the Lua C API. It’s used to mark modules, components, properties, functions, etc. To keep Meta simple and fast, it works using basic string operations. Meta does not preprocess C++ nor does it parse C++. This means valid C++ code can break Meta, if it’s not exactly as Meta expects it.

Running Meta

To build and run Meta, use the batch file in the scripts directory:

scripts\run_meta.bat

This script will:

  1. Initialize the Visual Studio developer environment
  2. Generate the solution if it doesn’t exist (using genie.exe)
  3. Build the meta project
  4. Run meta.exe from the project root directory

Meta scans src/ and plugins/ directories and generates:

Modules

To mark a class to be processed as a module by Meta, //@ module is used. It has 3 parameters:

  1. Name of the module’s struct.
  2. Module’s identifier.
  3. Label used in UI. This must be enclosed in “” since it can contain spaces.
//@ module AnimationModule animation "Animation"
struct AnimationModule : IModule {

It’s not necessary for struct to follow immediately after //@ module

//@ module GUIModule gui "GUI"

//@ component_struct id gui_canvas
struct Canvas {
	EntityRef entity;
	bool is_3d = false;				//@ property

//@ end or end of file mark end of module.

Between //@ module and its end, there can be multiple elements:

//@ enum

Enum can have one attribute named full, which makes Meta emit fully qualified enumerator names (keeping the enum’s scope) in the generated data instead of shortening them. This is necessary for enums nested in other structs or namespaces.

Values inside enums are automatically parsed by Meta.

Lua exposure

Enums marked with //@ enum are automatically exposed to Lua under the LumixAPI namespace. Each enum becomes a table with its enumerator names as keys and their integer values as values.

-- Access enum values in Lua
local align = LumixAPI.TextVAlign.MIDDLE    -- 1
local motion = LumixAPI.D6Motion.FREE       -- 2
local cursor = LumixAPI.CursorType.HAND     -- 6

Examples:

//@ enum
enum class TextVAlign : i32 {
	TOP,
	MIDDLE,
	BOTTOM
};
//@ module PhysicsModule physics "Physics"
struct PhysicsModule : IModule {
	//@ enum full PhysicsModule::D6Motion
	enum class D6Motion : i32 {
		LOCKED,
		LIMITED,
		FREE
	};

//@ functions

Marks all functions between //@ functions and //@ end for inclusion in the generated metadata. Only lines starting with virtual are included. Functions can have attributes, which are explained later.

Examples:

//@ functions
virtual EntityPtr raycast(const Vec3& origin, const Vec3& dir, float distance, EntityPtr ignore_entity) = 0;
virtual void setGravity(const Vec3& gravity) = 0;
//@ end

//@ include

When parsing a module, only the header file containing the module is included in the generated metadata. However, metadata may depend on types declared in other headers; for example, a function parameter type may come from another file. Use the //@ include "path/to/header.h" directive to add additional headers to the metadata.

Examples:

//@ module RenderModule renderer "Render"
//@ include "core/geometry.h"
//@ include "renderer/model.h"
namespace Lumix
{
...
//@ functions
// `Ray` is defined in "core/geometry.h"
virtual RayCastModelHit castRay(const Ray& ray, EntityPtr ignore) = 0;geometry.h"

//@ events

Marks all virtual methods between //@ events and //@ end for inclusion in the generated metadata as events.

Examples:

//@ events
virtual DelegateList<void(EntityRef)>& buttonClicked() = 0;
virtual DelegateList<void(EntityRef)>& rectHovered() = 0;
virtual DelegateList<void(EntityRef)>& rectHoveredOut() = 0;
virtual DelegateList<void(EntityRef, float, float)>& rectMouseDown() = 0;
virtual DelegateList<void(bool, i32, i32)>& mousedButtonUnhandled() = 0;
//@ end

//@ component

Defines new component type and includes all virtual methods between //@ component and //@ end in the genrated metadata.

Examples:

//@ component
virtual bool isPropertyAnimatorEnabled(EntityRef entity) = 0;
virtual void enablePropertyAnimator(EntityRef entity, bool enabled) = 0;
...
//@ end

//@ component has 1 parameter - Name - used to autodetect properties from method names. ID and label are generated from name and can be overriden with attributes //@ component Listener id audio_listener, //@ component Script id lua_script label "File".

Properties

Meta tries to autodetect properties from method names:

//@ array

Marks array properties. Must end with //@ end. //@ array has two parameters:

  1. Name - used to detect sub-properties.
  2. Identifier.

Each array must define the methods add + Name, remove + Name, and get + Name + Count. All other methods inside the array block are treated as sub-properties. Sub-property detection follows the same rules as ordinary property detection, but uses the array name instead of the component name.

//@ array Box boxes
virtual void addBox(EntityRef entity, int index) = 0;
virtual void removeBox(EntityRef entity, int index) = 0;
virtual int getBoxCount(EntityRef entity) = 0;
virtual Vec3 getBoxHalfExtents(EntityRef entity, int index) = 0;
virtual void setBoxHalfExtents(EntityRef entity, int index, const Vec3& size) = 0;
virtual Vec3 getBoxOffsetPosition(EntityRef entity, int index) = 0;			//@ label "Position offset"
virtual void setBoxOffsetPosition(EntityRef entity, int index, const Vec3& pos) = 0;
virtual Vec3 getBoxOffsetRotation(EntityRef entity, int index) = 0;			//@ radians label "Rotation offset"
virtual void setBoxOffsetRotation(EntityRef entity, int index, const Vec3& euler_angles) = 0;
//@ end

Functions in components

Any method inside a component block (//@ component//@ end) that is not detected as a property getter or setter is emitted in the generated metadata as a component function.

//@ component_struct

//@ component_struct declares a POD-style component whose data is stored directly in a C++ struct rather than accessed through virtual interface methods.

Rules and behavior:

Examples:

//@ component_struct
struct Fur {
	u32 layers = 16;		//@ property
	float scale = 0.01f;	//@ property
	float gravity = 1.f;	//@ property
	bool enabled = true;	//@ property
};
//@ end
//@ component_struct
struct EchoZone {
	EntityRef entity;
	float radius;		//@ property min 0
	float delay;		//@ property min 0 label "Delay (ms)"
};
//@ end

Attributes

Properties and functions in meta can have attributes. These are defined as //@ at the end of line. When both a getter and a setter belong to the same property, their attributes are merged (this overwrites attribute value if both getter and setter defines the attribute).

void setCameraFov(EntityRef entity, float fov); //@ min 0
float getCameraFov(EntityRef entity); //@ radians
// fov is is in radians and minimal value is 0

void setCameraNearPlane(EntityRef entity, float fov); //@ min 0
float getCameraNearPlane(EntityRef entity); //@ min 0.01
// near plane minimal value is 0.01, 0 on setter is overwritten by 0.01 on getter

Examples:

// attribute for getter
virtual Path getAmbientSoundClip(EntityRef entity) = 0;	//@ resource_type Clip::TYPE

// for setter
virtual void setAnimatorSource(EntityRef entity, const Path& path) = 0;	//@ resource_type anim::Controller::TYPE

// for function
virtual void pauseAmbientSound(EntityRef entity) = 0;					//@ alias pause

struct ChorusZone {
	// for field
	float radius;			//@ property min 0
};

function attribute

Forces Meta to treat the method strictly as a function rather than inferring it as a property getter or setter; this disables automatic property name detection for that method.

// this is not auto-detected as a getter for property `InputIndex`
virtual int getAnimatorInputIndex(EntityRef entity, const char* name) const = 0;	//@ function alias getInputIndex

getter, setter attributes

Allows explicit classification of a method as a property accessor and (optionally) supplies / overrides the property name. Use when the method name does not match automatic patterns or when you want to rename / merge differently named methods into one property.

Example:

	//@ component Text gui_text "Text" icon ICON_FA_FONT
	// getter for property `Text` on component `Text`, with automatic 
	// detection the method name would have to be getTextText
	virtual const char* getText(EntityRef entity) = 0;	//@ getter Text 
	virtual void setText(EntityRef entity, const char* text) = 0; //@ setter Text multiline

radians attribute

Marks that the numeric value is stored in radians; the editor/UI may display and edit it in degrees (converting automatically).

virtual Vec2 getD6JointSwingLimit(EntityRef entity) = 0; //@ radians
struct Camera {
	float fov; //@ property radians

resource_type attribute

Restricts a Path property so the editor accepts only resources of the specified type. Provide the engine resource type constant (e.g. Clip::TYPE, anim::Controller::TYPE) after resource_type.

virtual Path getAmbientSoundClip(EntityRef entity) = 0; //@ resource_type Clip::TYPE

color attribute

Marks that the value represents a color and should use a color picker in tools/UI.

Examples:

virtual Vec4 getImageColorRGBA(EntityRef entity) = 0;	//@ label "Color" color

no_ui attribute

Hides the property from editor/UI exposure while still generating metadata (available to scripting/serialization but not shown in tools).

virtual void setCurveDecalBezierP0(EntityRef entity, const Vec2& value) = 0; //@ no_ui

multiline attribute

Marks a string so the editor uses a multi-line text box.

virtual void setText(EntityRef entity, const char* text) = 0;			//@ setter Text multiline

min attribute

Specifies the minimum allowed value for a numeric property or return value.

virtual float getHingeJointDamping(EntityRef entity) = 0;	//@ min 0

alias attribute

Specifies an alternate function name; the alias does not need to be globally unique within the module.

clamp attribute

Specifies the minimum and the maximum allowed value for a numeric property or return value.

float fov;						//@ property radians clamp 0 360

label attribute

Sets a custom human‑readable UI name (can include spaces and different capitalization) for the targeted property or function instead of the automatically derived one.

virtual bool getRectClip(EntityRef entity) = 0;					//@ label "Clip content"

dynenum attribute

Marks the property as dynamic enum - its values are not compile-time constants. User must proved Name+Enum class which inherits from reflection::EnumAttribute;

virtual u32 getInstancedCubeLayer(EntityRef entity) = 0;				//@ dynenum Layer
	struct LayerEnum : reflection::EnumAttribute {
		u32 count(ComponentUID cmp) const override { 
			PhysicsModule* module = (PhysicsModule*)cmp.module;
			PhysicsSystem& system = (PhysicsSystem&)module->getSystem();
			return system.getCollisionsLayersCount();
		}
		const char* name(ComponentUID cmp, u32 idx) const override { 
			PhysicsModule* module = (PhysicsModule*)cmp.module;
			PhysicsSystem& system = (PhysicsSystem&)module->getSystem();
			return system.getCollisionLayerName(idx);
		}
	};

enum attribute

virtual GrassRotationMode getGrassRotationMode(EntityRef entity, int index) = 0;	//@ enum

Objects

//@ object marks a struct or class whose methods should be exposed to Lua scripting. Unlike modules and components (which represent game world systems), objects are standalone types that exist outside the ECS—such as editor plugins, utility classes, or system interfaces.

Methods marked with //@ function inside the object block are emitted in the generated Lua bindings.

Example:

//@ object
struct GUISystem : ISystem {
	virtual void setInterface(Interface* interface) = 0;
	//@ function
	virtual void enableCursor(bool enable) = 0;
	virtual Engine& getEngine() = 0;
};

In this example only enableCursor is exposed to Lua.

Accessing objects from Lua

Objects are accessed through module functions or global tables. For example, GUISystem is obtained via gui_module:getSystem():

-- Get GUISystem through the gui module and enable the cursor
this.world:getModule("gui"):getSystem():enableCursor(true)

Editor objects like SceneView and AssetBrowser are accessed through the global Editor table:

Editor.scene_view:setViewportPosition(pos)
Editor.asset_browser:openEditor("models/cube.fbx")

Binding objects in C++

Objects are bound to Lua using LuaWrapper::pushObject. The type name passed must match the name used in //@ object. Example from the editor:

lua_getglobal(L, "Editor");
StudioApp::GUIPlugin* scene_view = m_app.getGUIPlugin("scene_view");
LuaWrapper::pushObject(L, scene_view, "SceneView");
lua_setfield(L, -2, "scene_view");

LuaWrapper::pushObject(L, &m_app.getAssetBrowser(), "AssetBrowser");
lua_setfield(L, -2, "asset_browser");
lua_pop(L, 1);

This binds scene_view and asset_browser as fields on the global Editor table. The third argument to pushObject (e.g., "SceneView") must match the struct name marked with //@ object so Meta’s generated bindings are used.

Structs

//@ struct marks a plain data struct to be exposed to Lua. Unlike //@ component_struct (which defines ECS components), //@ struct is for standalone types used as function parameters or return values—such as raycast results or geometric primitives.

The struct must immediately follow on the next line. All fields are automatically parsed until the closing }. Each field must be on its own line with the format Type name;. Lines starting with using are skipped. No explicit //@ property marker is needed—all fields are exposed.

Difference from //@ object: Use //@ struct for plain data containers (POD-like types with public fields, passed by value or as results). Use //@ object for service/interface types with methods that need to be called from Lua. Structs expose fields; objects expose functions. Structs are passed by value, objects are passed by reference.

Usage in Lua:

-- getRay returns a Ray struct, castRay accepts it and returns RayCastModelHit struct
local ray = camera.camera:getRay(screen_pos)
local hit = this.world.renderer:castRay(ray, nil)
if hit.is_hit then
    -- access struct fields
    local pos = hit.origin + hit.dir * hit.t
end

Examples:

//@ struct
struct LUMIX_CORE_API Ray {
	DVec3 origin;
	Vec3 dir;
};
//@ struct
struct LUMIX_RENDERER_API RayCastModelHit {
	bool is_hit;
	float t;
	DVec3 origin;
	Vec3 dir;
	Mesh* mesh;
	EntityPtr entity;
	ComponentType component_type;
	u32 subindex;
};

Note on fields and pointers

Pointer and handle fields (e.g., Mesh*) are represented in Lua as tables containing lightuserdata (not raw userdata). A pointer field may be nil in Lua if it represents a null pointer in C++. When using pointer fields from Lua, check for nil before accessing or calling methods on them.

Example:

local ray = camera.camera:getRay(screen_pos)
local hit = this.world.renderer:castRay(ray, nil)
if hit.is_hit then
	if hit.mesh ~= nil then
		-- mesh is an opaque handle; use it only with API functions that accept Mesh
	end
end

Lifetime and ownership

Objects pushed to Lua with LuaWrapper::pushObject are raw pointers; C++ must guarantee their lifetime while Lua may hold references. Do not let Lua retain pointers to objects that may be destroyed by C++ without proper synchronization.

Caveats & Troubleshooting

Common issues:

Comparison Table

Feature //@ struct //@ object //@ component / //@ component_struct
Purpose Plain data types (POD) Service/interface types ECS component types
Exposes Fields (automatic) Methods (marked with //@ function) Properties and methods
Part of ECS No No Yes (attached to entities)
Field handling All fields auto-exposed N/A Fields need //@ property
Method handling N/A Explicit //@ function Auto-detected from names
Typical use Function params/returns Editor plugins, systems Game world data
Lua access Returned from functions Via modules or globals Via entity components