From 0c706715155a888f734b1b6503b5ed0358cec302 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sun, 15 Dec 2024 17:33:01 -0800 Subject: [PATCH] Fix layout of operator table, reorder contents of script manual --- docs/FlecsScript.md | 1162 +++++++++++++++++++++---------------------- 1 file changed, 581 insertions(+), 581 deletions(-) diff --git a/docs/FlecsScript.md b/docs/FlecsScript.md index ba62fa1a8..734dbd9d8 100644 --- a/docs/FlecsScript.md +++ b/docs/FlecsScript.md @@ -293,831 +293,831 @@ By default entity hierarchies are created with the `ChildOf` relationship. Other } ``` -## Advanced Features - -### Module statement -The `module` statement puts all contents of a script in a module. Example: +## Expressions +Scripts can contain expressions, which allow for computing values from inputs such as component values, template properties and variables. Here are some examples of valid Flecs script expressions: ```c -module components.transform - -// Creates components.transform.Position -struct Position { - x = f32 - y = f32 -} +const x = 10 + 20 * 30 +const x = 10 * (20 + 30) +const x = $var * 10 +const x = pow($var, 2) +const x = e.parent().name() +const x = Position: {10, 20} +const x = Position: {x: 10, y: 20} ``` -The `components.transform` entity will be created with the `Module` tag. +The following sections describe the different features of expressions. -### Using statement -The `using` keyword imports a namespace into the current namespace. Example: +### Operators +The following operators are supported in expressions, in order of precedence: + +| **Symbol** | **Description** | **Example** | +|------------|------------------------|------------------------| +| `!` | Logical NOT | `!10` | +| `*` | Multiplication | `10 * 20` | +| `/` | Division | `10 / 20` | +| `%` | Modulus | `10 % 3` | +| `+` | Addition | `10 + 20` | +| `-` | Subtraction/negative | `10 - 20`, `-(10, 20)` | +| `<<` | Bitwise left shift | `10 << 1` | +| `>>` | Bitwise right shift | `10 >> 1` | +| `>` | Greater than | `10 > 20` | +| `>=` | Greater than or equal | `10 >= 20` | +| `<` | Less than | `10 < 20` | +| `<=` | Less than or equal | `10 <= 20` | +| `==` | Equality | `10 == 20` | +| `!=` | Not equal | `10 != 20` | +| `&` | Bitwise AND | `2 & 6` | +| `\|` | Bitwise OR | `2 \| 4` | +| `&&` | Logical AND | `true && false` | +| `\|\|` | Logical OR | `true \|\| false` | + +### Values +The following table lists the different kinds of values that are supported in expressions: + +| **Value kind** | **Type** | **Example** | +|---------------------------|---------------|-----------------------------------| +| **Integer** | `i64` | `42`, `-100`, `0`, `0x1A` | +| **Floating Point** | `f64` | `3.14`, `-2.718`, `1e6`, `0.0` | +| **String** | `string` | `"Hello, World!"`, `"123"`, `""` | +| **Multiline string** | `string` | \``Hello World`\` | +| **Entity** | `entity` | `spaceship`, `spaceship.pilot` | +| **Enum/Bitmask values** | from lvalue | `Red`, `Blue`, `Lettuce \| Bacon` | +| **Composites** | from lvalue | `{x: 10, y: 20}`, `{10, 20}` | +| **Collections** | from lvalue | `[1, 2, 3]` | + +#### Initializers +Initializers are values that are used to initialize composite and collection members. Composite values are initialized by initializers that are delimited by `{}`, while collection initializers are delimited by `[]`. Furthermore, composite initializers can specify which member of the composite value should be initialized. Here are some examples of initializer expressions: ```c -// Without using -flecs.meta.struct Position { - x = flecs.meta.f32 - y = flecs.meta.f32 -} +{} +{10, 20} +{x: 10, y: 20} +{{10, 20}, {30, 40}} +{start: {x: 10, y: 20}, stop: {x: 10, y: 20}} +[10, 20, 30] +[{10, 20}, {30, 40}, {50, 60}] ``` -```c -// With using -using flecs.meta -struct Position { - x = f32 - y = f32 -} +Initializers must always be assigned to an lvalue of a well defined type. This can either be a typed variable, component assignment, function parameter or in the case of nested initializers, an element of another initializer. For example, this is a valid usage of an initializer: + +```c +const x = Position: {10, 20} ``` -The `using` keyword only applies to the scope in which it is specified. Example: +while this is an invalid usage of an initializer: ```c -// Scope without using -my_engine { - game.engines.FtlEngine: {active: true} -} +// Invalid, unknown type for initializer +const x = {10, 20} ``` + +When assigning variables to elements in a composite initializer, applications can use the following shorthand notation if the variable names are the same as the member name of the element: + ```c -// Scope with using -my_spaceship { - using game.engines +// Normal notation +Tree: {color: $color, height: $height} - FtlEngine: {active: true} -} + +// Shorthand notation +Tree: {color: $, height: $} ``` -A `using` statement may end with a wildcard (`*`). This will import all namespaces matching the path. Example: +### String interpolation +Flecs script supports interpolated strings, which are strings that can contain expressions. String interpolation supports two forms, where one allows for easy embedding of variables, whereas the other allows for embedding any kind of expression. The following example shows an embedded variable: ```c -using flecs.* - -struct Position { - x = f32 - y = f32 -} +const x = "The value of PI is $PI" ``` -### 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: +The following example shows how to use an expression: ```c -with SpaceShip { - MillenniumFalcon {} - UssEnterprise {} - UssVoyager {} - Rocinante {} -} +const x = "The circumference of the circle is {2 * $PI * $r}" ``` -This is equivalent to doing: +To prevent evaluating expressions in an interpolated string, the `$` and `{` characters can be escaped: ```c -MillenniumFalcon { - SpaceShip -} +const x = "The value of variable \$x is $x" +``` -UssEnterprise { - SpaceShip -} +### Types +The type of an expression is determined by the kind of expression, its operands and the context in which the expression is evaluated. The words "type" and "component" can be used interchangeably, as every type in Flecs is a component, and every component is a type. For component types to be used with scripts, they have to be described using the meta reflection addon. -UssVoyager { - SpaceShip -} +The following sections go over the different kinds of expressions and how their types are derived. -Rocinante { - SpaceShip -} -``` +#### Unary expressions +Unary expressions have a single operand, with the operator preceding it. The following table shows the different unary operators with the expression type: -With statements can contain multiple tags: +| **Operator** | **Expression Type** | +|--------------|-------------------------| +| `!` | `bool` | +| `-` | Same as operand. | -```c -with SpaceShip, HasWeapons { - MillenniumFalcon {} - UssEnterprise {} - UssVoyager {} - Rocinante {} -} -``` +#### Binary expressions +Binary expressions have two operands. The following table shows the different binary operators with the expression type. The operand type is the type to which the operands must be castable for it to be a valid expression. -With statements can contain component values, specified between parentheses: +| **Symbol** | **Expression type** | **Operand type** | +|------------|----------------------|----------------------| +| `*` | other (see below) | Numbers | +| `/` | `f64` | Numbers | +| `+` | other (see below) | Numbers | +| `-` | other (see below) | Numbers | +| `%` | `i64` | `i64` | +| `<<` | other (see below) | Integers | +| `>>` | other (see below) | Integers | +| `>` | `bool` | Numbers | +| `>=` | `bool` | Numbers | +| `<` | `bool` | Numbers | +| `<=` | `bool` | Numbers | +| `==` | `bool` | Values | +| `!=` | `bool` | Values | +| `&` | other (see below) | Integers | +| `\|` | other (see below) | Integers | +| `&&` | `bool` | `bool` | +| `\|\|` | `bool` | `bool` | + +For the operators where the expression type is listed as "other" the type is derived by going through these steps: +- If the types of the operands are equal, the expression type will be the operand type. +- If the types are different: + - For literal values, find the smallest storage type without losing precision. If operand types are now equal, use that. + - Find the most expressive type of the two operands (see below) + - If a cast to the most expressive type does not result in a loss of precision, use that. + - If the types are both numbers follow these rules in order: + - If one of the types is a floating point, use `f64` + - If one of the types is an integer, use `i64` + - If neither, throw a type incompatible error + +For equality expressions (using the `==` or `!=` operators), additional rules are used: + - If one of the operands is a bool, cast the other operand to a bool as well. This ensures that expressions such as `2 == true` evaluate to true. + - Equality expressions between floating point numbers are invalid + +Type expressiveness is determined by the kind of type and its storage size. The following tables show the expressiveness and storage scores: + +| **Type** | **Expressiveness Score** | +|--------------|---------------------------| +| bool | 1 | +| char | 2 | +| u8 | 2 | +| u16 | 3 | +| u32 | 4 | +| uptr | 5 | +| u64 | 6 | +| i8 | 7 | +| i16 | 8 | +| i32 | 9 | +| iptr | 10 | +| i64 | 11 | +| f32 | 12 | +| f64 | 13 | +| string | -1 | +| entity | -1 | + +| **Type** | **Storage Score** | +|--------------|-------------------| +| bool | 1 | +| char | 1 | +| u8 | 2 | +| u16 | 3 | +| u32 | 4 | +| uptr | 6 | +| u64 | 7 | +| i8 | 1 | +| i16 | 2 | +| i32 | 3 | +| iptr | 5 | +| i64 | 6 | +| f32 | 3 | +| f64 | 4 | +| string | -1 | +| entity | -1 | + +The function to determine whether a type is implicitly castable is: ```c -with Color(38, 25, 13) { - pillar_1 {} - pillar_2 {} - pillar_3 {} +bool implicit_cast_allowed(from, to) { + if (expressiveness(to) >= expressiveness(from)) { + return storage(to) >= storage(from); + } else { + return false; + } } ``` -### Variables -Scripts can contain variables, which are useful for often repeated values. Variables are created with the `const` keyword. Example: +If either the expressiveness or storage scores are negative, the operand types are not implicitly castable. -```c -const pi = 3.1415926 +#### Lvalues +Lvalues are the left side of assignments. There are two kinds of assignments possible in Flecs script: +- Variable initialization +- Initializer initialization -my_entity { - Rotation: {angle: $pi} -} +The type of an expression can be influenced by the type of the lvalue it is assigned to. For example, if the lvalue is a variable of type `Position`, the assigned initializer will also be of type `Position`: + +```c +const p = Position: {10, 20} ``` -Variables can be combined with expressions: +Similarly, when an initializer is used inside of an initializer, it obtains the type of the initializer element. In the following example the outer initializer is of type `Line`, while the inner initializers are of type `Point`: ```c -const pi = 3.1415926 -const pi_2 = $pi * 2 - -my_entity { - Rotation: {angle: $pi / 2} -} +const l = Line: {{10, 20}, {30, 40}} ``` -In the above examples, the type of the variable is inferred. Variables can also be provided with an explicit type: +Another notable example where this matters is for enum and bitmask constants. Consider the following example: ```c -const wood = Color: {38, 25, 13} +const c = Color: Red ``` -Variables can be used in component values as shown in the previous examples, or can be used directly as component. Example: +Here, `Red` is a resolvable identifier, even though the fully qualified identifier is `Color.Red`. However, because the type of the lvalue is of enum type `Color`, the expression `Red` will be resolved in the scope of `Color`. + +### Functions +Expressions can call functions. Functions in Flecs script can have arguments of any type, and must return a value. The following snippet shows examples of function calls: ```c -const wood = Color: {38, 25, 13} +const x = sqrt(100) +const x = pow(100, 2) +const x = add({10, 20}, {30, 40}) +``` -my_entity { - $wood -} +Currently functions can only be defined outside of scripts by the Flecs Script API. Flecs comes with a set of builtin and math functions. Math functions are defined by the script math addon, which must be explicitly enabled by defining `FLECS_SCRIPT_MATH`. -// is equivalent to +A function can be created in code by doing: -my_entity { - Color: {38, 25, 13} -} +```c +ecs_function(world, { + .name = "sum", + .return_type = ecs_id(ecs_i64_t), + .params = { + { .name = "a", .type = ecs_id(ecs_i64_t) }, + { .name = "b", .type = ecs_id(ecs_i64_t) } + }, + .callback = sum +}); ``` -Additionally, variables can also be used in combination with `with` statements: +### Methods +Methods are functions that are called on instances of the method's type. The first argument of a method is the instance on which the method is called. The following snippet shows examples of method calls: ```c -const wood = Color: {38, 25, 13} - -with $color { - pillar_1 {} - pillar_2 {} - pillar_3 {} -} +const x = v.length() +const x = v1.add(v2) ``` -### 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: +Just like functions, methods can currently only be defined outside of scripts by using the Flecs Script API. + +A method can be created in code by doing: ```c -grid { - Grid: { Game[Level].width, Game[Level].depth } -} +ecs_method(world, { + .name = "add", + .parent = ecs_id(ecs_i64_t), // Add method to i64 + .return_type = ecs_id(ecs_i64_t), + .params = { + { .name = "a", .type = ecs_id(ecs_i64_t) } + }, + .callback = sum +}); ``` -To reduce the number of component lookups in a script, the component value can be stored in a variable: +### Builtin functions and constants +The following table lists builtin core functions in the `flecs.script.core` namespace: -```c -const level = Game[Level] +| **Function Name** | **Description** | **Return Type** | **Arguments** | +|-------------------|-----------------------------|-----------------|-----------------------------| +| `pair` | Returns a pair identifier | `id` | (`entity`, `entity`) | -tiles { - Grid: { width: $level.width, $level.depth, prefab: Tile } -} -``` +The following table lists builtin methods on the `flecs.meta.entity` type: -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. +| **Method Name** | **Description** | **Return Type** | **Arguments** | +|-------------------|----------------------------------------|-----------------|----------------------| +| `name` | Returns entity name | `string` | `()` | +| `path` | Returns entity path | `string` | `()` | +| `parent` | Returns entity parent | `entity` | `()` | +| `has` | Returns whether entity has component | `bool` | `(id)` | -### If statement -Parts of a script can be conditionally executed with an if statement. Example: +The following table lists doc methods on the `flecs.meta.entity` type: -```c -const daytime = bool: false +| **Method Name** | **Description** | **Return Type** | **Arguments** | +|-------------------|------------------------------------------|------------------|----------------------| +| `doc_name` | Returns entity doc name | `string` | `()` | +| `doc_uuid` | Returns entity doc uuid | `string` | `()` | +| `doc_brief` | Returns entity doc brief description | `string` | `()` | +| `doc_detail` | Returns entity doc detailed description | `string` | `()` | +| `doc_link` | Returns entity doc link | `string` | `()` | +| `doc_color` | Returns entity doc color | `string` | `()` | + +To use the doc functions, make sure to use a Flecs build compiled with `FLECS_DOC` (enabled by default). + +The following table lists math functions in the `flecs.script.math` namespace: + +| **Function Name** | **Description** | **Return Type** | **Arguments** | +|--------------------|-----------------------------------------|-----------------|---------------------| +| `cos` | Compute cosine | `f64` | `(f64)` | +| `sin` | Compute sine | `f64` | `(f64)` | +| `tan` | Compute tangent | `f64` | `(f64)` | +| `acos` | Compute arc cosine | `f64` | `(f64)` | +| `asin` | Compute arc sine | `f64` | `(f64)` | +| `atan` | Compute arc tangent | `f64` | `(f64)` | +| `atan2` | Compute arc tangent with two parameters | `f64` | `(f64, f64)` | +| `cosh` | Compute hyperbolic cosine | `f64` | `(f64)` | +| `sinh` | Compute hyperbolic sine | `f64` | `(f64)` | +| `tanh` | Compute hyperbolic tangent | `f64` | `(f64)` | +| `acosh` | Compute area hyperbolic cosine | `f64` | `(f64)` | +| `asinh` | Compute area hyperbolic sine | `f64` | `(f64)` | +| `atanh` | Compute area hyperbolic tangent | `f64` | `(f64)` | +| `exp` | Compute exponential function | `f64` | `(f64)` | +| `ldexp` | Generate value from significant and exponent | `f64` | `(f64, f32)` | +| `log` | Compute natural logarithm | `f64` | `(f64)` | +| `log10` | Compute common logarithm | `f64` | `(f64)` | +| `exp2` | Compute binary exponential function | `f64` | `(f64)` | +| `log2` | Compute binary logarithm | `f64` | `(f64)` | +| `pow` | Raise to power | `f64` | `(f64, f64)` | +| `sqrt` | Compute square root | `f64` | `(f64)` | +| `sqr` | Compute square | `f64` | `(f64)` | +| `ceil` | Round up value | `f64` | `(f64)` | +| `floor` | Round down value | `f64` | `(f64)` | +| `round` | Round to nearest | `f64` | `(f64)` | +| `abs` | Compute absolute value | `f64` | `(f64)` | -lantern { - Color: {210, 255, 200} +The following table lists the constants in the `flecs.script.math` namespace: - if $daytime { - Emissive: { value: 0 } - } else { - Emissive: { value: 1 } - } -} -``` +| **Function Name** | **Description** | **Type** | **Value** | +|-------------------|-------------------------------------------|----------|----------------------| +| `E` | Euler's number | `f64` | `2.71828182845904523536028747135266250` | +| `PI` | Ratio of circle circumference to diameter | `f64` | `3.14159265358979323846264338327950288` | -### For statement -Parts of a script can be repeated with a for loop. Example: +To use the math functions, make sure to use a Flecs build compiled with the `FLECS_SCRIPT_MATH` addon (disabled by default) and that the module is imported: ```c -for i in 0..10 { - Lantern() { - Position: {x: $i * 5} - } -} +ECS_IMPORT(world, FlecsScriptMath); ``` -The values specified in the range can be an expression: +## Templates +Templates are parameterized scripts that can be used to create procedural assets. Templates can be created with the `template` keyword. Example: ```c -for i in 0..$count { - // ... +template Square { + Color: {255, 0, 0} + Rectangle: {width: 100, height: 100} } ``` -When creating entities in a for loop, ensure that they are unique or the for loop will overwrite the same entity: +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: ```c -for i in 0..10 { - // overwrites entity "e" 10 times - e: { Position: {x: $i * 5} } +my_entity { + Square } -``` -To avoid this, scripts can either create anonymous entities: +// is equivalent to -```c -for i in 0..10 { - // creates 10 anonymous entities - _ { Position: {x: $i * 5} } +my_entity { + Color: {255, 0, 0} + Rectangle: {width: 100, height: 100} } ``` -Or use a unique string expression for the entity name: +Templates are commonly used in combination with the kind syntax: ```c -for i in 0..10 { - // creates entities with names e_0, e_1, ... e_9 - "e_$i" { Position: {x: $i * 5} } -} +Square my_entity ``` -### 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: +Templates can be parameterized with properties. Properties are variables that are exposed as component members. To create a property, use the `prop` keyword. Example: ```c -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: +template Square { + prop size = i32: 10 + prop color = Color: {255, 0, 0} -```c -// Create a PositionList tag with a DefaultChildComponent -PositionList { - DefaultChildComponent: {Position} + $color + Rectangle: {width: $size, height: $size} } -// Derive default component for scope from PositionList -PositionList plist { - ent_a = 10, 20 - ent_b = 10, 20 - ent_c = 10, 20 -} +Square my_entity(size: 20, color: {38, 25, 13}) ``` -A common use of default components is when creating structs. `struct` is a component with `member` as default child component. Example: +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: ```c -struct Position { - x = f32 - y = f32 -} - -// is equivalent to +template Tree { + prop height = f32: 10 -struct Position { - member x(f32) - member y(f32) -} -``` + const wood_color = Color: {38, 25, 13} + const leaves_color = Color: {51, 76, 38} -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: + const canopy_height = 2 + const trunk_height = $height - $canopy_height + const trunk_width = 2 -```c -struct Line { - start { - x = f32 - y = f32 + Trunk { + Position: {0, ($height / 2), 0} + Rectangle: {$trunk_width, $trunk_height} + $wood_color } - stop { - x = f32 - y = f32 + + Canopy { + const canopy_y = $trunk_height + ($canopy_height / 2) + + Position3: {0, $canopy_y, 0} + Box: {$canopy_width, $canopy_height} + $leaves_color } } -// is equivalent to +template Forest { + Tree(height: 5) { + Position: {x: -10} + } -struct Line { - member start { - member x(f32) - member y(f32) + Tree(height: 10) { + Position: {x: 0} } - member stop { - member x(f32) - member y(f32) + + Tree(height: 7) { + Position: {x: 10} } } -``` - -### Semicolon operator -Multiple statements can be combined on a single line when using the semicolon operator. Example: -```c -my_spaceship { - SpaceShip; HasFtl -} +Forest my_forest ``` -### Comma operator -The comma operator can be used as a shortcut to create multiple entities in a scope. Example: +## Advanced Features -```c -my_spaceship { - pilot_a, - pilot_b, - pilot_c -} +### Module statement +The `module` statement puts all contents of a script in a module. Example: -// is equivalent to +```c +module components.transform -my_spaceship { - pilot_a {} - pilot_b {} - pilot_c {} +// Creates components.transform.Position +struct Position { + x = f32 + y = f32 } ``` -This allows for a more natural way to describe things like enum types: - -```c -enum Color { - Red, - Green, - Blue -} +The `components.transform` entity will be created with the `Module` tag. -// is equivalent to +### Using statement +The `using` keyword imports a namespace into the current namespace. Example: -enum Color { - constant Red - constant Green - constant Blue +```c +// Without using +flecs.meta.struct Position { + x = flecs.meta.f32 + y = flecs.meta.f32 } ``` - -## Expressions -Scripts can contain expressions, which allow for computing values from inputs such as component values, template properties and variables. Here are some examples of valid Flecs script expressions: - ```c -const x = 10 + 20 * 30 -const x = 10 * (20 + 30) -const x = $var * 10 -const x = pow($var, 2) -const x = e.parent().name() -const x = Position: {10, 20} -const x = Position: {x: 10, y: 20} -``` - -The following sections describe the different features of expressions. - -### Operators -The following operators are supported in expressions, in order of precedence: - -| **Symbol** | **Description** | **Example** | -|------------|------------------------|------------------------| -| `!` | Logical NOT | `!10` | -| `*` | Multiplication | `10 * 20` | -| `/` | Division | `10 / 20` | -| `%` | Modulus | `10 % 3` | -| `+` | Addition | `10 + 20` | -| `-` | Subtraction/negative | `10 - 20`, `-(10, 20)` | -| `<<` | Bitwise left shift | `10 << 1` | -| `>>` | Bitwise right shift | `10 >> 1` | -| `>` | Greater than | `10 > 20` | -| `>=` | Greater than or equal | `10 >= 20` | -| `<` | Less than | `10 < 20` | -| `<=` | Less than or equal | `10 <= 20` | -| `==` | Equality | `10 == 20` | -| `!=` | Not equal | `10 != 20` | -| `&` | Bitwise AND | `2 & 6` | -| `\|` | Bitwise OR | `2 | 4` | -| `&&` | Logical AND | `true && false` | -| `\|\|` | Logical OR | `true || false` | - -### Values -The following table lists the different kinds of values that are supported in expressions: +// With using +using flecs.meta -| **Value kind** | **Type** | **Example** | -|---------------------------|---------------|-----------------------------------| -| **Integer** | `i64` | `42`, `-100`, `0`, `0x1A` | -| **Floating Point** | `f64` | `3.14`, `-2.718`, `1e6`, `0.0` | -| **String** | `string` | `"Hello, World!"`, `"123"`, `""` | -| **Multiline string** | `string` | \``Hello World`\` | -| **Entity** | `entity` | `spaceship`, `spaceship.pilot` | -| **Enum/Bitmask values** | from lvalue | `Red`, `Blue`, `Lettuce \| Bacon` | -| **Composites** | from lvalue | `{x: 10, y: 20}`, `{10, 20}` | -| **Collections** | from lvalue | `[1, 2, 3]` | +struct Position { + x = f32 + y = f32 +} +``` -#### Initializers -Initializers are values that are used to initialize composite and collection members. Composite values are initialized by initializers that are delimited by `{}`, while collection initializers are delimited by `[]`. Furthermore, composite initializers can specify which member of the composite value should be initialized. Here are some examples of initializer expressions: +The `using` keyword only applies to the scope in which it is specified. Example: ```c -{} -{10, 20} -{x: 10, y: 20} -{{10, 20}, {30, 40}} -{start: {x: 10, y: 20}, stop: {x: 10, y: 20}} -[10, 20, 30] -[{10, 20}, {30, 40}, {50, 60}] +// Scope without using +my_engine { + game.engines.FtlEngine: {active: true} +} ``` +```c +// Scope with using +my_spaceship { + using game.engines -Initializers must always be assigned to an lvalue of a well defined type. This can either be a typed variable, component assignment, function parameter or in the case of nested initializers, an element of another initializer. For example, this is a valid usage of an initializer: + FtlEngine: {active: true} +} +``` + +A `using` statement may end with a wildcard (`*`). This will import all namespaces matching the path. Example: ```c -const x = Position: {10, 20} +using flecs.* + +struct Position { + x = f32 + y = f32 +} ``` -while this is an invalid usage of an initializer: +### 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: ```c -// Invalid, unknown type for initializer -const x = {10, 20} +with SpaceShip { + MillenniumFalcon {} + UssEnterprise {} + UssVoyager {} + Rocinante {} +} ``` -When assigning variables to elements in a composite initializer, applications can use the following shorthand notation if the variable names are the same as the member name of the element: +This is equivalent to doing: ```c -// Normal notation -Tree: {color: $color, height: $height} - +MillenniumFalcon { + SpaceShip +} -// Shorthand notation -Tree: {color: $, height: $} -``` +UssEnterprise { + SpaceShip +} -### String interpolation -Flecs script supports interpolated strings, which are strings that can contain expressions. String interpolation supports two forms, where one allows for easy embedding of variables, whereas the other allows for embedding any kind of expression. The following example shows an embedded variable: +UssVoyager { + SpaceShip +} -```c -const x = "The value of PI is $PI" +Rocinante { + SpaceShip +} ``` -The following example shows how to use an expression: +With statements can contain multiple tags: ```c -const x = "The circumference of the circle is {2 * $PI * $r}" +with SpaceShip, HasWeapons { + MillenniumFalcon {} + UssEnterprise {} + UssVoyager {} + Rocinante {} +} ``` -To prevent evaluating expressions in an interpolated string, the `$` and `{` characters can be escaped: +With statements can contain component values, specified between parentheses: ```c -const x = "The value of variable \$x is $x" +with Color(38, 25, 13) { + pillar_1 {} + pillar_2 {} + pillar_3 {} +} ``` -### Types -The type of an expression is determined by the kind of expression, its operands and the context in which the expression is evaluated. The words "type" and "component" can be used interchangeably, as every type in Flecs is a component, and every component is a type. For component types to be used with scripts, they have to be described using the meta reflection addon. +### Variables +Scripts can contain variables, which are useful for often repeated values. Variables are created with the `const` keyword. Example: -The following sections go over the different kinds of expressions and how their types are derived. +```c +const pi = 3.1415926 -#### Unary expressions -Unary expressions have a single operand, with the operator preceding it. The following table shows the different unary operators with the expression type: +my_entity { + Rotation: {angle: $pi} +} +``` -| **Operator** | **Expression Type** | -|--------------|-------------------------| -| `!` | `bool` | -| `-` | Same as operand. | +Variables can be combined with expressions: -#### Binary expressions -Binary expressions have two operands. The following table shows the different binary operators with the expression type. The operand type is the type to which the operands must be castable for it to be a valid expression. +```c +const pi = 3.1415926 +const pi_2 = $pi * 2 -| **Symbol** | **Expression type** | **Operand type** | -|------------|----------------------|----------------------| -| `*` | other (see below) | Numbers | -| `/` | `f64` | Numbers | -| `+` | other (see below) | Numbers | -| `-` | other (see below) | Numbers | -| `%` | `i64` | `i64` | -| `<<` | other (see below) | Integers | -| `>>` | other (see below) | Integers | -| `>` | `bool` | Numbers | -| `>=` | `bool` | Numbers | -| `<` | `bool` | Numbers | -| `<=` | `bool` | Numbers | -| `==` | `bool` | Values | -| `!=` | `bool` | Values | -| `&` | other (see below) | Integers | -| `\|` | other (see below) | Integers | -| `&&` | `bool` | `bool` | -| `\|\|` | `bool` | `bool` | +my_entity { + Rotation: {angle: $pi / 2} +} +``` -For the operators where the expression type is listed as "other" the type is derived by going through these steps: -- If the types of the operands are equal, the expression type will be the operand type. -- If the types are different: - - For literal values, find the smallest storage type without losing precision. If operand types are now equal, use that. - - Find the most expressive type of the two operands (see below) - - If a cast to the most expressive type does not result in a loss of precision, use that. - - If the types are both numbers follow these rules in order: - - If one of the types is a floating point, use `f64` - - If one of the types is an integer, use `i64` - - If neither, throw a type incompatible error +In the above examples, the type of the variable is inferred. Variables can also be provided with an explicit type: -For equality expressions (using the `==` or `!=` operators), additional rules are used: - - If one of the operands is a bool, cast the other operand to a bool as well. This ensures that expressions such as `2 == true` evaluate to true. - - Equality expressions between floating point numbers are invalid +```c +const wood = Color: {38, 25, 13} +``` -Type expressiveness is determined by the kind of type and its storage size. The following tables show the expressiveness and storage scores: +Variables can be used in component values as shown in the previous examples, or can be used directly as component. Example: -| **Type** | **Expressiveness Score** | -|--------------|---------------------------| -| bool | 1 | -| char | 2 | -| u8 | 2 | -| u16 | 3 | -| u32 | 4 | -| uptr | 5 | -| u64 | 6 | -| i8 | 7 | -| i16 | 8 | -| i32 | 9 | -| iptr | 10 | -| i64 | 11 | -| f32 | 12 | -| f64 | 13 | -| string | -1 | -| entity | -1 | +```c +const wood = Color: {38, 25, 13} -| **Type** | **Storage Score** | -|--------------|-------------------| -| bool | 1 | -| char | 1 | -| u8 | 2 | -| u16 | 3 | -| u32 | 4 | -| uptr | 6 | -| u64 | 7 | -| i8 | 1 | -| i16 | 2 | -| i32 | 3 | -| iptr | 5 | -| i64 | 6 | -| f32 | 3 | -| f64 | 4 | -| string | -1 | -| entity | -1 | +my_entity { + $wood +} -The function to determine whether a type is implicitly castable is: +// is equivalent to -```c -bool implicit_cast_allowed(from, to) { - if (expressiveness(to) >= expressiveness(from)) { - return storage(to) >= storage(from); - } else { - return false; - } +my_entity { + Color: {38, 25, 13} } ``` -If either the expressiveness or storage scores are negative, the operand types are not implicitly castable. - -#### Lvalues -Lvalues are the left side of assignments. There are two kinds of assignments possible in Flecs script: -- Variable initialization -- Initializer initialization - -The type of an expression can be influenced by the type of the lvalue it is assigned to. For example, if the lvalue is a variable of type `Position`, the assigned initializer will also be of type `Position`: +Additionally, variables can also be used in combination with `with` statements: ```c -const p = Position: {10, 20} +const wood = Color: {38, 25, 13} + +with $color { + pillar_1 {} + pillar_2 {} + pillar_3 {} +} ``` -Similarly, when an initializer is used inside of an initializer, it obtains the type of the initializer element. In the following example the outer initializer is of type `Line`, while the inner initializers are of type `Point`: +### 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: ```c -const l = Line: {{10, 20}, {30, 40}} +grid { + Grid: { Game[Level].width, Game[Level].depth } +} ``` -Another notable example where this matters is for enum and bitmask constants. Consider the following example: +To reduce the number of component lookups in a script, the component value can be stored in a variable: ```c -const c = Color: Red +const level = Game[Level] + +tiles { + Grid: { width: $level.width, $level.depth, prefab: Tile } +} ``` -Here, `Red` is a resolvable identifier, even though the fully qualified identifier is `Color.Red`. However, because the type of the lvalue is of enum type `Color`, the expression `Red` will be resolved in the scope of `Color`. +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. -### Functions -Expressions can call functions. Functions in Flecs script can have arguments of any type, and must return a value. The following snippet shows examples of function calls: +### If statement +Parts of a script can be conditionally executed with an if statement. Example: ```c -const x = sqrt(100) -const x = pow(100, 2) -const x = add({10, 20}, {30, 40}) +const daytime = bool: false + +lantern { + Color: {210, 255, 200} + + if $daytime { + Emissive: { value: 0 } + } else { + Emissive: { value: 1 } + } +} ``` -Currently functions can only be defined outside of scripts by the Flecs Script API. Flecs comes with a set of builtin and math functions. Math functions are defined by the script math addon, which must be explicitly enabled by defining `FLECS_SCRIPT_MATH`. - -A function can be created in code by doing: +### For statement +Parts of a script can be repeated with a for loop. Example: ```c -ecs_function(world, { - .name = "sum", - .return_type = ecs_id(ecs_i64_t), - .params = { - { .name = "a", .type = ecs_id(ecs_i64_t) }, - { .name = "b", .type = ecs_id(ecs_i64_t) } - }, - .callback = sum -}); +for i in 0..10 { + Lantern() { + Position: {x: $i * 5} + } +} ``` -### Methods -Methods are functions that are called on instances of the method's type. The first argument of a method is the instance on which the method is called. The following snippet shows examples of method calls: +The values specified in the range can be an expression: ```c -const x = v.length() -const x = v1.add(v2) +for i in 0..$count { + // ... +} ``` -Just like functions, methods can currently only be defined outside of scripts by using the Flecs Script API. - -A method can be created in code by doing: +When creating entities in a for loop, ensure that they are unique or the for loop will overwrite the same entity: ```c -ecs_method(world, { - .name = "add", - .parent = ecs_id(ecs_i64_t), // Add method to i64 - .return_type = ecs_id(ecs_i64_t), - .params = { - { .name = "a", .type = ecs_id(ecs_i64_t) } - }, - .callback = sum -}); +for i in 0..10 { + // overwrites entity "e" 10 times + e: { Position: {x: $i * 5} } +} ``` -### Builtin functions and constants -The following table lists builtin core functions in the `flecs.script.core` namespace: - -| **Function Name** | **Description** | **Return Type** | **Arguments** | -|-------------------|-----------------------------|-----------------|-----------------------------| -| `pair` | Returns a pair identifier | `id` | (`entity`, `entity`) | - -The following table lists builtin methods on the `flecs.meta.entity` type: - -| **Method Name** | **Description** | **Return Type** | **Arguments** | -|-------------------|----------------------------------------|-----------------|----------------------| -| `name` | Returns entity name | `string` | `()` | -| `path` | Returns entity path | `string` | `()` | -| `parent` | Returns entity parent | `entity` | `()` | -| `has` | Returns whether entity has component | `bool` | `(id)` | - -The following table lists doc methods on the `flecs.meta.entity` type: - -| **Method Name** | **Description** | **Return Type** | **Arguments** | -|-------------------|------------------------------------------|------------------|----------------------| -| `doc_name` | Returns entity doc name | `string` | `()` | -| `doc_uuid` | Returns entity doc uuid | `string` | `()` | -| `doc_brief` | Returns entity doc brief description | `string` | `()` | -| `doc_detail` | Returns entity doc detailed description | `string` | `()` | -| `doc_link` | Returns entity doc link | `string` | `()` | -| `doc_color` | Returns entity doc color | `string` | `()` | - -To use the doc functions, make sure to use a Flecs build compiled with `FLECS_DOC` (enabled by default). +To avoid this, scripts can either create anonymous entities: -The following table lists math functions in the `flecs.script.math` namespace: +```c +for i in 0..10 { + // creates 10 anonymous entities + _ { Position: {x: $i * 5} } +} +``` -| **Function Name** | **Description** | **Return Type** | **Arguments** | -|--------------------|-----------------------------------------|-----------------|---------------------| -| `cos` | Compute cosine | `f64` | `(f64)` | -| `sin` | Compute sine | `f64` | `(f64)` | -| `tan` | Compute tangent | `f64` | `(f64)` | -| `acos` | Compute arc cosine | `f64` | `(f64)` | -| `asin` | Compute arc sine | `f64` | `(f64)` | -| `atan` | Compute arc tangent | `f64` | `(f64)` | -| `atan2` | Compute arc tangent with two parameters | `f64` | `(f64, f64)` | -| `cosh` | Compute hyperbolic cosine | `f64` | `(f64)` | -| `sinh` | Compute hyperbolic sine | `f64` | `(f64)` | -| `tanh` | Compute hyperbolic tangent | `f64` | `(f64)` | -| `acosh` | Compute area hyperbolic cosine | `f64` | `(f64)` | -| `asinh` | Compute area hyperbolic sine | `f64` | `(f64)` | -| `atanh` | Compute area hyperbolic tangent | `f64` | `(f64)` | -| `exp` | Compute exponential function | `f64` | `(f64)` | -| `ldexp` | Generate value from significant and exponent | `f64` | `(f64, f32)` | -| `log` | Compute natural logarithm | `f64` | `(f64)` | -| `log10` | Compute common logarithm | `f64` | `(f64)` | -| `exp2` | Compute binary exponential function | `f64` | `(f64)` | -| `log2` | Compute binary logarithm | `f64` | `(f64)` | -| `pow` | Raise to power | `f64` | `(f64, f64)` | -| `sqrt` | Compute square root | `f64` | `(f64)` | -| `sqr` | Compute square | `f64` | `(f64)` | -| `ceil` | Round up value | `f64` | `(f64)` | -| `floor` | Round down value | `f64` | `(f64)` | -| `round` | Round to nearest | `f64` | `(f64)` | -| `abs` | Compute absolute value | `f64` | `(f64)` | +Or use a unique string expression for the entity name: -The following table lists the constants in the `flecs.script.math` namespace: +```c +for i in 0..10 { + // creates entities with names e_0, e_1, ... e_9 + "e_$i" { Position: {x: $i * 5} } +} +``` -| **Function Name** | **Description** | **Type** | **Value** | -|-------------------|-------------------------------------------|----------|----------------------| -| `E` | Euler's number | `f64` | `2.71828182845904523536028747135266250` | -| `PI` | Ratio of circle circumference to diameter | `f64` | `3.14159265358979323846264338327950288` | +### 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. -To use the math functions, make sure to use a Flecs build compiled with the `FLECS_SCRIPT_MATH` addon (disabled by default) and that the module is imported: +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: ```c -ECS_IMPORT(world, FlecsScriptMath); +with Position { + ent_a = 10, 20 + ent_b = 20, 30 +} ``` -## Templates -Templates are parameterized scripts that can be used to create procedural assets. Templates can be created with the `template` keyword. Example: +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: ```c -template Square { - Color: {255, 0, 0} - Rectangle: {width: 100, height: 100} +// 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 } ``` -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: +A common use of default components is when creating structs. `struct` is a component with `member` as default child component. Example: ```c -my_entity { - Square +struct Position { + x = f32 + y = f32 } // is equivalent to -my_entity { - Color: {255, 0, 0} - Rectangle: {width: 100, height: 100} +struct Position { + member x(f32) + member y(f32) } ``` -Templates are commonly used in combination with the kind syntax: +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: ```c -Square my_entity +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) + } +} ``` -Templates can be parameterized with properties. Properties are variables that are exposed as component members. To create a property, use the `prop` keyword. Example: +### Semicolon operator +Multiple statements can be combined on a single line when using the semicolon operator. Example: ```c -template Square { - prop size = i32: 10 - prop color = Color: {255, 0, 0} - - $color - Rectangle: {width: $size, height: $size} +my_spaceship { + SpaceShip; HasFtl } - -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: +### Comma operator +The comma operator can be used as a shortcut to create multiple entities in a scope. Example: ```c -template Tree { - prop height = f32: 10 - - const wood_color = Color: {38, 25, 13} - const leaves_color = Color: {51, 76, 38} +my_spaceship { + pilot_a, + pilot_b, + pilot_c +} - const canopy_height = 2 - const trunk_height = $height - $canopy_height - const trunk_width = 2 +// is equivalent to - Trunk { - Position: {0, ($height / 2), 0} - Rectangle: {$trunk_width, $trunk_height} - $wood_color - } +my_spaceship { + pilot_a {} + pilot_b {} + pilot_c {} +} +``` - Canopy { - const canopy_y = $trunk_height + ($canopy_height / 2) +This allows for a more natural way to describe things like enum types: - Position3: {0, $canopy_y, 0} - Box: {$canopy_width, $canopy_height} - $leaves_color - } +```c +enum Color { + Red, + Green, + Blue } -template Forest { - Tree(height: 5) { - Position: {x: -10} - } - - Tree(height: 10) { - Position: {x: 0} - } +// is equivalent to - Tree(height: 7) { - Position: {x: 10} - } +enum Color { + constant Red + constant Green + constant Blue } - -Forest my_forest ``` ## API