Flecs: Flecs Script
Description: A fast entity component system (ECS) for C & C++

| Flecs v4.0 A fast entity component system (ECS) for C & C++

Loading...
Searching...
No Matches
Flecs Script

# Introduction

Flecs Script is a runtime interpreted DSL for creating entities and components that is optimized for defining scenes, assets and configuration. In a nutshell, Flecs Script is to ECS what HTML/JSX is to a browser.

Some of the features of Flecs Script are:

- Native support for named entities, hierarchies and inheritance
- Assign component values
- Expressions and variables (`$var + 10`)
- Conditionals (`if $var > 10`)
- Native integration with templates (procedural assets)

To learn Flecs Script, check out the Tutorial!

# Example

using flecs.meta
struct MaxSpeed {
value = f32
}
struct Position {
x = f32
y = f32
}
Prefab SpaceShip {
MaxSpeed: {value: 100}
cockpit {
Position: {x: -10, y: 0}
}
}
my_spaceship : SpaceShip {
Position: {x: 10, y: 20}
}

# The Basics

This section goes over the basic syntax over Flecs Script.

## Entities

An entity is created by specifying an identifier followed by a scope. Example:

my_entity {}

An entity scope can contain components and child entities. The following example shows how to add a child entity:

my_parent {
my_child {}
}

Note how a scope is also added to the child entity.

To create anonymous entities, use the `_` identifier:

_ {
my_child {} // named child with anonymous parent
}

## Tags

A tag can be added to an entity by simply specifying the tag's identifier in an entity scope. Example:

SpaceShip {} // Define SpaceShip tag
my_entity {
SpaceShip // Add SpaceShip to my_entity
}

## Pairs

Pairs are added to entities by adding them to an entity scope, just like tags:

Likes {}
Pizza {}
my_entity {
(Likes, Pizza)
}

## Components

Components are specified like tags, but with an additional value:

my_entity {
Position: {x: 10, y: 20}
}

For a component to be assignable with a value, it also needs to be described in the reflection framework.

A component can also be added without a value. This will create a default constructed component. Example:

my_entity {
Position
}

Components can be defined in a script:

using flecs.meta
struct Position {
x = f32
y = f32
}
my_entity {
Position: {x: 10, y: 20}
}

Components can be pairs:

my_entity {
(Start, Position): {x: 0, y: 0}
(Stop, Position): {x: 10, y: 20}
}

## Namespacing

When referring to child entities or components, identifiers need to include the parent path as well as the entity name. Paths are provided as lists of identifiers separated by a dot (`.`):

Sun.Earth {
solarsystem.Planet
}

To avoid having to repeatedly type the same paths, use the `using` statement (see below).

## Singletons

To create a singleton component, use `$` as the entity identifier:

$ {
TimeOfDay: { t: 0.5 }
}

Multiple singleton components can be specified in the same scope:

$ {
TimeOfDay: { t: 0.5 }
Player: { name: "bob" }
}

## Entity kinds

An entity can be created with a "kind", which is a component specified before the entity name. This is similar to adding a tag or component in a scope, but can provide a more natural way to describe things. For example:

SpaceShip my_spaceship {}

This is equivalent to doing:

my_spaceship {
SpaceShip
}

When using the entity kind syntax, the scope is optional:

SpaceShip my_spaceship // no {}

If the specified kind is a component, a value can be specified between parentheses:

CheckBox my_checkbox(checked: true)

When the entity kind is a component, a value will always be assigned even if none is specified. This is different from component assignments in a scope. Example:

CheckBox my_checkbox(checked: true)
// is equivalent to
my_checkbox {
CheckBox: {checked: true}
}
CheckBox my_checkbox
// is equivalent to
my_checkbox {
CheckBox: {}
}

### Builtin kinds

Applications can specify the following builtin kinds which provide convenience shortcuts to commonly used features:

prefab SpaceShip
// is equivalent to
Prefab spaceship
prefab SpaceShip {
slot CockPit
}
// is equivalent to
prefab SpaceShip {
CockPit {
(SlotOf, SpaceShip)
}
}

## Inheritance

Scripts can natively specify inheritance relationships between entities, which is useful in particular for prefabs. Example:

Prefab SpaceShip {
MaxSpeed: {value: 100}
}
my_spaceship : SpaceShip {}

When specifying inheritance, the scope is optional:

my_spaceship : SpaceShip // no {}

This is equivalent to doing:

my_spaceship {
(IsA, SpaceShip)
}

## Relationship hierarchies

By default entity hierarchies are created with the `ChildOf` relationship. Other relationships can also be used to create hierarchies by combining a pair with a scope. Example:

(IsA, Thing) {
(IsA, Organism) {
(IsA, Plant) {
Tree {}
}
(IsA, Animal) {
Human {}
}
}
}

# Advanced Features

## Module statement

The `module` statement puts all contents of a script in a module. Example:

module components.transform
// Creates components.transform.Position
struct Position {
x = f32
y = f32
}

The `components.transform` entity will be created with the `Module` tag.

## Using statement

The `using` keyword imports a namespace into the current namespace. Example:

// Without using
flecs.meta.struct Position {
x = flecs.meta.f32
y = flecs.meta.f32
}
// With using
using flecs.meta
struct Position {
x = f32
y = f32
}

The `using` keyword only applies to the scope in which it is specified. Example:

// Scope without using
my_engine {
game.engines.FtlEngine: {active: true}
}
// Scope with using
my_spaceship {
using game.engines
FtlEngine: {active: true}
}

A `using` statement may end with a wildcard (`*`). This will import all namespaces matching the path. Example:

using flecs.*
struct Position {
x = f32
y = f32
}

## With statement

When you're building a scene or asset you may find yourself often repeating the same components for multiple entities. To avoid this, a `with` statement can be used. For example:

with SpaceShip {
MillenniumFalcon {}
UssEnterprise {}
UssVoyager {}
Rocinante {}
}

This is equivalent to doing:

MillenniumFalcon {
SpaceShip
}
UssEnterprise {
SpaceShip
}
UssVoyager {
SpaceShip
}
Rocinante {
SpaceShip
}

With statements can contain multiple tags:

with SpaceShip, HasWeapons {
MillenniumFalcon {}
UssEnterprise {}
UssVoyager {}
Rocinante {}
}

With statements can contain component values, specified between parentheses:

with Color(38, 25, 13) {
pillar_1 {}
pillar_2 {}
pillar_3 {}
}

## Variables

Scripts can contain variables, which are useful for often repeated values. Variables are created with the `const` keyword. Example:

const pi = 3.1415926
my_entity {
Rotation: {angle: $pi}
}

Variables can be combined with expressions:

const pi = 3.1415926
const pi_2 = $pi * 2
my_entity {
Rotation: {angle: $pi / 2}
}

In the above examples, the type of the variable is inferred. Variables can also be provided with an explicit type:

const wood = Color: {38, 25, 13}

Variables can be used in component values as shown in the previous examples, or can be used directly as component. Example:

const wood = Color: {38, 25, 13}
my_entity {
$wood
}
// is equivalent to
my_entity {
Color: {38, 25, 13}
}

Additionally, variables can also be used in combination with `with` statements:

const wood = Color: {38, 25, 13}
with $color {
pillar_1 {}
pillar_2 {}
pillar_3 {}
}

## Component values

A script can use the value of a component that is looked up on a specific entity. The following example fetches the `width` and `depth` members from the `Level` component, that is fetched from the `Game` entity:

grid {
Grid: { Game[Level].width, Game[Level].depth }
}

To reduce the number of component lookups in a script, the component value can be stored in a variable:

const level = Game[Level]
tiles {
Grid: { width: $level.width, $level.depth, prefab: Tile }
}

The requested component is stored by value, not by reference. Adding or removing components to the entity will not invalidate the component data. If the requested component does not exist on the entity, script execution will fail.

## If statement

Parts of a script can be conditionally executed with an if statement. Example:

const daytime = bool: false
lantern {
Color: {210, 255, 200}
if $daytime {
Emissive: { value: 0 }
} else {
Emissive: { value: 1 }
}
}

## Default components

A scope can have a default component, which means entities in that scope can assign values of that component without having to specify the component name.

There are different ways to specify a default component. One way is to use a `with` statement. Default component values are assigned with the `=` operator, and don't need a `{}` surrounding the value. Example:

with Position {
ent_a = 10, 20
ent_b = 20, 30
}

Another way a default components are derived is from the entity kind. If an entity is specified with a kind, a `DefaultChildComponent` component will be looked up on the kind to find the default component for the scope, if any. For example:

// Create a PositionList tag with a DefaultChildComponent
PositionList {
DefaultChildComponent: {Position}
}
// Derive default component for scope from PositionList
PositionList plist {
ent_a = 10, 20
ent_b = 10, 20
ent_c = 10, 20
}
EcsDefaultChildComponent
When added to an entity this informs serialization formats which component to use when a value is ass...
Definition flecs.h:1465

A common use of default components is when creating structs. `struct` is a component with `member` as default child component. Example:

struct Position {
x = f32
y = f32
}
// is equivalent to
struct Position {
member x(f32)
member y(f32)
}

Note how `member` is also used as kind for the children. This means that children of `x` and `y` derive their default child component from `member`, which is set to `member`. This makes it easy to create nested members:

struct Line {
start {
x = f32
y = f32
}
stop {
x = f32
y = f32
}
}
// is equivalent to
struct Line {
member start {
member x(f32)
member y(f32)
}
member stop {
member x(f32)
member y(f32)
}
}
flecs::component::member
untyped_component & member(flecs::entity_t type_id, flecs::entity_t unit, const char *name, int32_t count=0)
Add member with unit.
Definition untyped_component.inl:46
flecs::system::start
void start()
Start timer.
flecs::system::stop
void stop()
Stop timer.

## Semicolon operator

Multiple statements can be combined on a single line when using the semicolon operator. Example:

my_spaceship {
SpaceShip; HasFtl
}

# Comma operator

The comma operator can be used as a shortcut to create multiple entities in a scope. Example:

my_spaceship {
pilot_a,
pilot_b,
pilot_c
}
// is equivalent to
my_spaceship {
pilot_a {}
pilot_b {}
pilot_c {}
}

This allows for a more natural way to describe things like enum types:

enum Color {
Red,
Green,
Blue
}
// is equivalent to
enum Color {
constant Red
constant Green
constant Blue
}

# Templates

Templates are parametrized scripts that can be used to create procedural assets. Templates can be created with the `template` keyword. Example:

template Square {
Color: {255, 0, 0}
Rectangle: {width: 100, height: 100}
}

The script contents of an template are not ran immediately. Instead they are ran whenever an template is instantiated. To instantiate an template, add it as a regular component to an entity:

my_entity {
Square
}
// is equivalent to
my_entity {
Color: {255, 0, 0}
Rectangle: {width: 100, height: 100}
}

Templates are commonly used in combination with the kind syntax:

Square my_entity

Templates can be parametrized with properties. Properties are variables that are exposed as component members. To create a property, use the `prop` keyword. Example:

template Square {
prop size = i32: 10
prop color = Color: {255, 0, 0}
$color
Rectangle: {width: $size, height: $size}
}
Square my_entity(size: 20, color: {38, 25, 13})

Template scripts can do anything a regular script can do, including creating child entities. The following example shows how to create an template that uses a nested template to create children:

template Tree {
prop height = f32: 10
const wood_color = Color: {38, 25, 13}
const leaves_color = Color: {51, 76, 38}
const canopy_height = 2
const trunk_height = $height - $canopy_height
const trunk_width = 2
Trunk {
Position: {0, ($height / 2), 0}
Rectangle: {$trunk_width, $trunk_height}
$wood_color
}
Canopy {
const canopy_y = $trunk_height + ($canopy_height / 2)
Position3: {0, $canopy_y, 0}
Box: {$canopy_width, $canopy_height}
$leaves_color
}
}
template Forest {
Tree(height: 5) {
Position: {x: -10}
}
Tree(height: 10) {
Position: {x: 0}
}
Tree(height: 7) {
Position: {x: 10}
}
}
Forest my_forest

# API

This section goes over how to run scripts in an application.

## Run once

To run a script once, use the `ecs_script_run` function. Example:

const char *code = "my_spaceship {}";
if (ecs_script_run(world, "my_script_name", code)) {
// error
}
ecs_script_run
FLECS_API int ecs_script_run(ecs_world_t *world, const char *name, const char *code)
Parse script.
flecs::world
The world.
Definition world.hpp:137

Alternatively a script can be ran directly from a file:

if (ecs_script_run_file(world, "my_script.flecs")) {
// error
}
ecs_script_run_file
FLECS_API int ecs_script_run_file(ecs_world_t *world, const char *filename)
Parse script file.

If a script fails, the entities created by the script will not be automatically deleted. When a script contains templates, script resources will not get cleaned up until the entities associated with the templates are deleted.

## Run multiple times

A script can be ran multiple times by using the `ecs_script_parse` and `ecs_script_eval` functions. Example:

const char *code = "my_spaceship {}";
ecs_script_t *script = ecs_script_parse(
world, "my_script_name", code);
if (!script) {
// error
}
if (ecs_script_eval(script)) {
// error
}
// Run again
if (ecs_script_eval(script)) {
}
// Free script resources
ecs_script_free(script);
ecs_script_free
FLECS_API void ecs_script_free(ecs_script_t *script)
Free script.
ecs_script_parse
FLECS_API ecs_script_t * ecs_script_parse(ecs_world_t *world, const char *name, const char *code)
Parse script.
ecs_script_eval
FLECS_API int ecs_script_eval(ecs_script_t *script)
Evaluate script.
script
script_builder script(const char *name=nullptr) const
Build script.
Definition mixin.inl:31
ecs_script_t
Script object.
Definition script.h:59

If a script fails, the entities created by the script will not be automatically deleted. When a script contains templates, script resources will not get cleaned up until the entities associated with the templates are deleted.

## Managed script

Managed scripts are scripts that are associated with an entity, and can be ran multiple times. Entities created by a managed script are tagged with the script. When script execution fails, the entities associated with the script will be deleted. Additionally, if after executing the script again an entity is no longer created by the script, it will also be deleted.

To run a managed script, do:

const char *code = "my_spaceship {}";
ecs_entity_t s = ecs_script(world, {
.code = code
});
if (!s) {
// error
}
ecs_entity_t
ecs_id_t ecs_entity_t
An entity identifier.
Definition flecs.h:347

To update the code of a managed script, use the `ecs_script_update` function:

if (ecs_script_update(world, s, 0, new_code)) {
// error
}
ecs_script_update
FLECS_API int ecs_script_update(ecs_world_t *world, ecs_entity_t script, ecs_entity_t instance, const char *code)
Update script with new code.

When a script contains templates, script resources will not get cleaned up until the entities associated with the templates are deleted.