LumixEngine

Meta

Meta scans the source code for lines containing //@ and generates reflection data and scripting APIs. It’s used to mark modules, components, properties, functions, and types. To keep Meta simple and fast, it works using basic string operations. Meta does not preprocess C++ nor does it fully parse C++. This means valid C++ can break Meta if it is not written in the form Meta expects.

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/ and generates:

Generated files must not be edited directly. Change the annotated C++ declaration or the relevant generator and run Meta again.

Generator plugins

src/meta/meta.cpp owns source scanning, parsing, reflection generation, and the shared component type files. Scripting backends and other optional generators use the Meta plugin flow instead of being called specially by main:

The current generators use this flow for Evox.

A generator receives the parsed declarations through MetaData and registers a callback with META_PLUGIN:

#include "meta.h"

namespace {

void generateExample(MetaData& data) {
	OutputStream out;
	for (Module& module : data.modules) {
		// Generate output from the parsed module.
	}
	writeFile("path/to/output.gen.h", out);
}

} // namespace

META_PLUGIN(generateExample)

MetaData provides the parsed modules, structs, objects, and global enums. Generator source files placed in src/meta/ are included in the meta project by the build wildcard. Shared helpers such as writeFile, formatCPP, consumeArg, and forEachArg are declared in src/meta/meta.h.

All registered callbacks run after parsing and core reflection generation. A backend decides which declarations and C++ types it supports; for example, Evox skips unsupported signatures and prints a diagnostic during generation.

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.

Script exposure

Enums marked with //@ enum are available to binding generators. Evox emits strongly typed enum declarations and imports them into generated core: units that use the enum.

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

Evox functions

The Evox generator emits supported module functions into the module’s core: unit. Native Span<T> return values are represented by generated iterators, so callers can use a for loop without retaining the native span:

import "core:physics"

fn processPhysics(module : PhysicsModule) : void {
	for hit in module.getTriggerHits() {
		// use hit
	}
}

Consult the generated file in data/scripts/core/ for the exact Evox name and signature. A declaration is omitted if its types cannot be marshalled by the Evox backend.

//@ 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 overridden with attributes such as //@ component Listener id audio_listener or //@ component Zone id trigger_zone label "Trigger zone".

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

Type aliases

Mark a simple C++ using declaration at the top level or inside a module metadata block (outside object/struct/function blocks):

//@alias
using SoundHandle = i32;

The declaration must be on the next line. MetaData::aliases exposes these names and underlying types to generators. Evox currently supports aliases (including chains) of bool, u8, i32, int, u32, and float. Every supported alias becomes a distinct single-field extern struct in its own lowercase core: unit, and generated signatures use that wrapper:

// core:soundhandle
extern struct SoundHandle { value : i32; }

Use .value to access the scalar, or a typed struct literal to construct a handle. Alias chains each get their own distinct wrapper with the resolved scalar field (not nested wrappers). Imports are generated wherever aliases are used, including struct fields. The wrapper has the same size/alignment and call-frame representation as the scalar, so native values, mutable references, and spans retain their existing marshalling. Native C++ signatures retain their aliases. Alias names must be globally unique; ambiguous names, cycles, and unsupported underlying types remain unsupported. Pointer/reference aliases, templates, typedef, and scope inference are not supported.

This is separate from the alias function attribute, which renames a method.

Objects

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

Methods marked with //@ function inside the object block are considered for generated bindings. Each backend emits only signatures it supports.

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 available to scripting generators. See the backend documentation for supported signatures and object representation.

Structs

//@ struct marks a plain data struct for use by scripting backends. 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 callable methods. Structs expose fields; objects expose functions. Structs are passed by value, while objects wrap references or pointers according to the scripting backend.

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;
};

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
Script access Returned from functions Via generated object handles Via module/entity component handles